# Copyright 2026 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import json
import unittest
from unittest.mock import patch, MagicMock, mock_open
from pathlib import Path
import sys
import os

# Mocking sys.exit to avoid test runner exit
# Importing worktree
import worktree


class TestWorktree(unittest.TestCase):
  @patch("worktree.subprocess.run")
  def test_run_cmd(self, mock_run):
    worktree.run_cmd(["echo", "hi"])
    mock_run.assert_called_once_with(
      ["echo", "hi"], cwd=None, check=True, text=True, capture_output=False
    )

  @patch("worktree.subprocess.run")
  def test_run_cmd_no_check_failure(self, mock_run):
    import subprocess

    mock_run.side_effect = subprocess.CalledProcessError(1, ["false"])
    result = worktree.run_cmd(["false"], check=False)
    self.assertIsNone(result)

  @patch("pathlib.Path.is_dir", return_value=True)
  @patch("pathlib.Path.exists", side_effect=[True, True, True])
  @patch("pathlib.Path.resolve")
  def test_validate_parent_repo_success(
    self, mock_resolve, mock_exists, mock_is_dir
  ):
    mock_resolve.return_value = Path("/fake/repo")
    parent_dir, g_config, s_dir = worktree.validate_parent_repo("/fake/repo")
    self.assertEqual(parent_dir, Path("/fake/repo"))
    self.assertEqual(g_config, Path("/fake/repo/.gclient"))
    self.assertEqual(s_dir, Path("/fake/repo/src"))

  @patch("pathlib.Path.is_dir", return_value=True)
  @patch("pathlib.Path.exists", side_effect=[True, True, True])
  @patch("pathlib.Path.resolve")
  def test_validate_parent_repo_from_src(
    self, mock_resolve, mock_exists, mock_is_dir
  ):
    # Simulate being called from /fake/repo/src
    mock_resolve.return_value = Path("/fake/repo/src")
    parent_dir, g_config, s_dir = worktree.validate_parent_repo(
      "/fake/repo/src"
    )
    self.assertEqual(parent_dir, Path("/fake/repo"))
    self.assertEqual(g_config, Path("/fake/repo/.gclient"))
    self.assertEqual(s_dir, Path("/fake/repo/src"))

  @patch(
    "pathlib.Path.read_text",
    return_value='solutions = []\ncache_dir = "/tmp/cache"',
  )
  def test_check_cache_dir_present(self, mock_read):
    with patch("builtins.print") as mock_print:
      worktree.check_cache_dir(Path("fake/.gclient"))
      # Should not print warning
      self.assertFalse(
        any("WARNING" in str(call) for call in mock_print.call_args_list)
      )

  @patch("pathlib.Path.read_text", return_value='solutions = []')
  def test_check_cache_dir_missing(self, mock_read):
    with patch("builtins.print") as mock_print:
      worktree.check_cache_dir(Path("fake/.gclient"))
      # Should print warning
      mock_print.assert_any_call(
        "\nWARNING: 'cache_dir' not found in your .gclient file."
      )

  @patch("worktree.run_cmd")
  def test_create_worktree(self, mock_run):
    worktree.create_worktree("parent/src", "variant/src", "my_branch")
    mock_run.assert_called_with(
      ["git", "worktree", "add", "variant/src", "-b", "my_branch"],
      cwd="parent/src",
    )

  @patch("worktree.run_cmd")
  def test_create_worktree_with_base(self, mock_run):
    worktree.create_worktree(
      "parent/src", "variant/src", "my_branch", "base_branch"
    )
    # Should call git worktree add AND git branch --set-upstream-to
    self.assertEqual(mock_run.call_count, 2)
    mock_run.assert_any_call(
      [
        "git",
        "worktree",
        "add",
        "variant/src",
        "-b",
        "my_branch",
        "base_branch",
      ],
      cwd="parent/src",
    )
    mock_run.assert_any_call(
      ["git", "branch", "--set-upstream-to", "base_branch", "my_branch"],
      cwd="variant/src",
    )

  def test_setup_vscode_color(self):
    with (
      patch("pathlib.Path.mkdir"),
      patch("pathlib.Path.exists", return_value=False),
      patch("pathlib.Path.write_text") as mock_write,
    ):
      variant_src = Path("/tmp/src")
      worktree.setup_vscode_color(variant_src, "red")

      # Check if it writes valid JSON with titleBar colors
      args, _ = mock_write.call_args
      content = json.loads(args[0])
      self.assertEqual(
        content["workbench.colorCustomizations"]["titleBar.activeBackground"],
        "#3a1414",
      )

  @patch("worktree.Path.exists")
  def test_get_next_variant_name(self, mock_exists):
    # Simulate 'bling_1' exists (True), but 'bling_2' does not (False)
    mock_exists.side_effect = [True, False]
    parent_dir = Path("/Users/vincb/bling")

    name = worktree.get_next_variant_name(parent_dir)
    self.assertEqual(name, "bling_2")

  @patch("worktree.validate_parent_repo")
  @patch("worktree.check_cache_dir")
  @patch("worktree.Path.mkdir")
  @patch("worktree.create_worktree")
  @patch("shutil.copy2")
  @patch("worktree.run_cmd")
  @patch("worktree.Path.exists")
  def test_main_internal_base(
    self,
    mock_exists,
    mock_run,
    mock_copy,
    mock_create,
    mock_mkdir,
    mock_check,
    mock_validate,
  ):
    # Mocking validate_parent_repo
    parent_dir = Path("/fake/repo")
    gclient_config = parent_dir / ".gclient"
    parent_src = parent_dir / "src"
    mock_validate.return_value = (parent_dir, gclient_config, parent_src)
    mock_check.return_value = True

    # Mocking variant setup
    # 1. get_next_variant_name (will be called in main)
    # 3. internal_src.exists() (will be True)
    mock_exists.side_effect = [False, False, True]

    # Mocking args
    with patch(
      "sys.argv",
      ["worktree.py", "/fake/repo", "--internal-base", "internal_base_branch"],
    ):
      worktree.main()

      # Verify internal logic
      # run_cmd should be called for:
      # 1. gclient sync
      # 2. git checkout -b ... (internal)
      # 3. git branch --set-upstream-to ... (internal)

      # Find the index of internal commands in mock_run.call_args_list
      calls = [call.args[0] for call in mock_run.call_args_list]
      self.assertIn(
        ["git", "checkout", "-b", "repo_1", "internal_base_branch"], calls
      )
      self.assertIn(
        [
          "git",
          "branch",
          "--set-upstream-to",
          "internal_base_branch",
          "repo_1",
        ],
        calls,
      )

    @patch("worktree.validate_parent_repo")
    @patch("worktree.check_cache_dir")
    @patch("worktree.Path.mkdir")
    @patch("worktree.create_worktree")
    @patch("shutil.copy2")
    @patch("worktree.run_cmd")
    @patch("worktree.Path.exists")
    def test_main_internal_base_fetch_from_parent(
      self,
      mock_exists,
      mock_run,
      mock_copy,
      mock_create,
      mock_mkdir,
      mock_check,
      mock_validate,
    ):
      # Mocking validate_parent_repo
      parent_dir = Path("/fake/repo")
      gclient_config = parent_dir / ".gclient"
      parent_src = parent_dir / "src"
      mock_validate.return_value = (parent_dir, gclient_config, parent_src)
      mock_check.return_value = True

      # Mocking variant setup
      # 1. get_next_variant_name (will be called in main)
      # 2. variant_root.exists() (will be False)
      # 3. internal_src.exists() (will be True)
      mock_exists.side_effect = [False, False, True]

      # Mocking run_cmd:
      # 1. validate_parent_repo might call run_cmd (it doesn't in our mocks)
      # 2. git show-ref --verify refs/heads/internal_base_branch (return None to simulate missing)
      # 3. git remote add parent ...
      # 4. git fetch parent ...
      # 5. git checkout -b ...
      # 6. git branch --set-upstream-to ...
      mock_run.side_effect = [
        None,  # git show-ref returns None (captured=True)
        None,  # git remote add
        None,  # git fetch
        None,  # git checkout
        None,  # git branch --set-upstream-to
      ]

      # Mocking args
      with patch(
        "sys.argv",
        [
          "worktree.py",
          "/fake/repo",
          "--internal-base",
          "internal_base_branch",
        ],
      ):
        worktree.main()

        # Verify ios_internal logic
        calls = [call.args[0] for call in mock_run.call_args_list]
        self.assertIn(
          ["git", "remote", "add", "parent", str(parent_src / "internal")],
          calls,
        )
        self.assertIn(["git", "fetch", "parent", "internal_base_branch"], calls)
        self.assertIn(["git", "checkout", "-b", "repo_1", "FETCH_HEAD"], calls)
        self.assertIn(
          [
            "git",
            "branch",
            "--set-upstream-to",
            "parent/internal_base_branch",
            "repo_1",
          ],
          calls,
        )


if __name__ == "__main__":
  unittest.main()
