from __future__ import annotations

import json
import unittest
from pathlib import Path
from typing import Sequence

from mais_humana.cli import main
from mais_humana.repository_mesh import (
    CommandResult,
    MeshActionKind,
    MeshEnvironment,
    MeshEnvironmentKind,
    MeshErrorKind,
    MeshPresence,
    MeshRiskLevel,
    RepositoryTarget,
    automation_markdown,
    build_mesh_report,
    classify_command_error,
    command_is_destructive,
    default_repository_targets,
    mesh_actions_csv,
    mesh_inventory_csv,
    mesh_markdown,
    mesh_orders_payload,
    mesh_summary_payload,
    normalize_remote_url,
    repository_mesh_artifact_records,
    run_repository_mesh,
    validate_report,
)
from tests.helpers import make_tmp


def make_repo(root: Path, name: str) -> Path:
    repo = root / name
    (repo / ".git").mkdir(parents=True)
    return repo


class FakeGit:
    def __init__(self) -> None:
        self.calls: list[tuple[str, ...]] = []
        self.status_by_repo: dict[str, tuple[str, ...]] = {}
        self.remote_by_repo: dict[str, str] = {}
        self.head_by_repo: dict[str, str] = {}
        self.branch_by_repo: dict[str, str] = {}
        self.upstream_by_repo: dict[str, str] = {}
        self.ahead_behind_by_repo: dict[str, str] = {}
        self.fetch_error_by_repo: dict[str, str] = {}

    def set_repo(
        self,
        repo: Path,
        *,
        branch: str = "main",
        head: str = "abc1230000000000000000000000000000000000",
        remote: str = "https://git.ami.app.br/admin/repo.git",
        status: Sequence[str] = (),
        upstream: str = "origin/main",
        ahead_behind: str = "0 0",
        fetch_error: str = "",
    ) -> None:
        key = str(repo)
        self.branch_by_repo[key] = branch
        self.head_by_repo[key] = head
        self.remote_by_repo[key] = remote
        self.status_by_repo[key] = tuple(status)
        self.upstream_by_repo[key] = upstream
        self.ahead_behind_by_repo[key] = ahead_behind
        if fetch_error:
            self.fetch_error_by_repo[key] = fetch_error

    def __call__(self, argv: Sequence[str], cwd: Path | None = None, timeout: int = 60) -> CommandResult:
        del cwd, timeout
        args = tuple(str(item) for item in argv)
        self.calls.append(args)
        try:
            repo = args[args.index("-C") + 1]
        except ValueError:
            repo = ""
        command = args[args.index(repo) + 1 :] if repo else args
        if command[:3] == ("fetch", "--all", "--prune"):
            if repo in self.fetch_error_by_repo:
                stderr = self.fetch_error_by_repo[repo]
                return CommandResult(args, 1, "", stderr, error_kind=classify_command_error(stderr, 1))
            return CommandResult(args, 0, "", "")
        if command[:2] == ("branch", "--show-current"):
            return CommandResult(args, 0, self.branch_by_repo.get(repo, "main") + "\n", "")
        if command[:2] == ("rev-parse", "HEAD"):
            return CommandResult(args, 0, self.head_by_repo.get(repo, "abc123") + "\n", "")
        if command[:3] == ("remote", "get-url", "origin"):
            return CommandResult(args, 0, self.remote_by_repo.get(repo, "https://git.ami.app.br/admin/repo.git") + "\n", "")
        if command[:2] == ("status", "--short"):
            return CommandResult(args, 0, "\n".join(self.status_by_repo.get(repo, ())) + "\n", "")
        if command[:3] == ("log", "-1", "--oneline"):
            return CommandResult(args, 0, self.head_by_repo.get(repo, "abc123")[:7] + " test\n", "")
        if command[:4] == ("rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"):
            upstream = self.upstream_by_repo.get(repo, "origin/main")
            if upstream:
                return CommandResult(args, 0, upstream + "\n", "")
            return CommandResult(args, 1, "", "fatal: no upstream configured", error_kind=MeshErrorKind.UNKNOWN)
        if command[:3] == ("rev-list", "--left-right", "--count"):
            return CommandResult(args, 0, self.ahead_behind_by_repo.get(repo, "0 0") + "\n", "")
        return CommandResult(args, 1, "", "unexpected command", error_kind=MeshErrorKind.UNKNOWN)


class RepositoryMeshTests(unittest.TestCase):
    def test_default_targets_include_nominal_reconciliation_cases(self) -> None:
        targets = default_repository_targets()
        names = {item.declared_name: item for item in targets}
        self.assertIn("tudo-para-ia-mais-humana-platform", names)
        self.assertEqual(names["tudo-para-ia-mais-humana-platform"].expected_local_name, "tudo-para-ia-mais-humana-platform")
        self.assertIn("tudo-para-ia-mais-humana", names["tudo-para-ia-mais-humana-platform"].aliases)
        self.assertIn("tudo-para-ia-mais-humana-plataform", names["tudo-para-ia-mais-humana-platform"].aliases)
        self.assertTrue(names["tudo-para-ia-mais-humana-platform"].requires_nominal_reconciliation)
        self.assertIn("tudo-para-ia-integracoes-platform", names)
        self.assertEqual(names["tudo-para-ia-integracoes-platform"].canonical_name, "tudo-para-ia-integracoes-plataform")

    def test_remote_normalization_ignores_git_suffix_and_slash(self) -> None:
        self.assertEqual(
            normalize_remote_url("https://git.ami.app.br/admin/repo.git/"),
            normalize_remote_url("https://git.ami.app.br/admin/repo"),
        )

    def test_error_classifier_handles_dubious_credentials_auth_network_and_repo(self) -> None:
        self.assertEqual(classify_command_error("fatal: detected dubious ownership", 1), MeshErrorKind.DUBIOUS_OWNERSHIP)
        self.assertEqual(classify_command_error("SEC_E_NO_CREDENTIALS credenciais nao disponiveis", 1), MeshErrorKind.CREDENTIALS_MISSING)
        self.assertEqual(classify_command_error("Authentication failed for https://x", 1), MeshErrorKind.AUTHENTICATION)
        self.assertEqual(classify_command_error("Could not resolve host: git.ami.app.br", 1), MeshErrorKind.NETWORK)
        self.assertEqual(classify_command_error("fatal: not a git repository", 1), MeshErrorKind.NOT_A_REPOSITORY)
        self.assertEqual(classify_command_error("", 0), MeshErrorKind.NONE)

    def test_command_destructive_guard_allows_fetch_and_ff_only_but_blocks_reset_pull_clean(self) -> None:
        self.assertFalse(command_is_destructive("git fetch --all --prune"))
        self.assertFalse(command_is_destructive("git merge --ff-only @{u}"))
        self.assertTrue(command_is_destructive("git reset --hard HEAD"))
        self.assertTrue(command_is_destructive("git clean -fdx"))
        self.assertTrue(command_is_destructive("git pull origin main"))
        self.assertTrue(command_is_destructive("git checkout main"))
        self.assertTrue(command_is_destructive("git restore ."))

    def test_clean_repository_gets_safe_fetch_action(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(
            repo,
            remote="https://git.ami.app.br/admin/alpha.git",
            head="1111111111111111111111111111111111111111",
        )
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=fake, fetch=True)
        self.assertEqual(report.ok_count, 1)
        self.assertEqual(report.blocked_count, 0)
        self.assertEqual(report.summaries[0].risk, MeshRiskLevel.OK)
        self.assertTrue(any(action.kind == MeshActionKind.FETCH for action in report.summaries[0].actions))
        self.assertTrue(any("--prune" in " ".join(call) for call in fake.calls))
        self.assertFalse(validate_report(report))

    def test_dirty_repository_blocks_destructive_sync(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(
            repo,
            remote="https://git.ami.app.br/admin/alpha.git",
            status=(" M README.md", "?? src/new.py"),
        )
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=fake, fetch=False)
        summary = report.summaries[0]
        self.assertEqual(summary.risk, MeshRiskLevel.BLOCKED)
        self.assertEqual(summary.dirty_count, 1)
        self.assertTrue(any(action.kind == MeshActionKind.BLOCK_DESTRUCTIVE_SYNC for action in summary.actions))
        self.assertIn("working tree sujo", mesh_markdown(report))

    def test_divergent_branch_blocks_automatic_reconciliation(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(
            repo,
            remote="https://git.ami.app.br/admin/alpha.git",
            ahead_behind="2 3",
        )
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=fake, fetch=False)
        self.assertEqual(report.summaries[0].risk, MeshRiskLevel.BLOCKED)
        reasons = " ".join(action.reason for action in report.summaries[0].actions)
        self.assertIn("ahead/behind", reasons)
        self.assertIn("bloqueios contra sync destrutiva", mesh_markdown(report))

    def test_remote_mismatch_creates_fix_remote_action_without_auto_execution(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/wrong.git")
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=fake, fetch=False)
        actions = report.summaries[0].actions
        self.assertEqual(report.summaries[0].remote_mismatch_count, 1)
        fix_actions = [item for item in actions if item.kind == MeshActionKind.FIX_REMOTE_URL]
        self.assertEqual(len(fix_actions), 1)
        self.assertFalse(fix_actions[0].can_execute_automatically)
        self.assertIn("git remote set-url origin", " ".join(fix_actions[0].command_preview))

    def test_missing_repository_creates_clone_plan_but_not_success(self) -> None:
        root = make_tmp()
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=FakeGit(), fetch=False)
        summary = report.summaries[0]
        self.assertEqual(summary.risk, MeshRiskLevel.ATTENTION)
        self.assertEqual(summary.missing_count, 1)
        self.assertTrue(any(action.kind == MeshActionKind.CLONE_MISSING for action in summary.actions))
        self.assertIn("espelho ausente", mesh_markdown(report))

    def test_unreachable_environment_is_blocked_as_external_access(self) -> None:
        root = make_tmp()
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("server", MeshEnvironmentKind.CODEX_SERVER, str(root / "missing-root"), "remote", local=False)
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=FakeGit(), fetch=False)
        summary = report.summaries[0]
        self.assertEqual(summary.risk, MeshRiskLevel.BLOCKED)
        self.assertEqual(summary.observations[0].presence, MeshPresence.UNREACHABLE_ENVIRONMENT)
        self.assertTrue(any(action.kind == MeshActionKind.REQUIRE_ENVIRONMENT_ACCESS for action in summary.actions))

    def test_present_non_git_directory_blocks_replace_or_clone(self) -> None:
        root = make_tmp()
        (root / "alpha").mkdir()
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=FakeGit(), fetch=False)
        summary = report.summaries[0]
        self.assertEqual(summary.risk, MeshRiskLevel.BLOCKED)
        self.assertEqual(summary.observations[0].presence, MeshPresence.PRESENT_NOT_GIT)
        self.assertTrue(any(action.destructive for action in summary.actions))

    def test_alias_materialization_creates_nominal_rename_action(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha-old")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git")
        target = RepositoryTarget(
            "alpha-new",
            "admin/alpha",
            "alpha-new",
            "01_alpha",
            aliases=("alpha-old",),
            canonical_name="alpha-new",
            requires_nominal_reconciliation=True,
        )
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=fake, fetch=False)
        summary = report.summaries[0]
        self.assertEqual(summary.nominal_mismatch_count, 1)
        self.assertTrue(any(action.kind == MeshActionKind.RENAME_LOCAL_FOLDER for action in summary.actions))
        self.assertIn("materializado como alias", mesh_markdown(report))

    def test_fetch_credential_error_is_reported_as_blocker_not_plugin_blocker(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(
            repo,
            remote="https://git.ami.app.br/admin/alpha.git",
            fetch_error="fatal: unable to access url: SEC_E_NO_CREDENTIALS",
        )
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(
            root,
            targets=(target,),
            environments=(env,),
            runner=fake,
            fetch=True,
            plugin_auth_attempt="user rejected MCP tool call",
        )
        self.assertEqual(report.credential_errors, 1)
        self.assertEqual(report.summaries[0].risk, MeshRiskLevel.BLOCKED)
        md = mesh_markdown(report)
        self.assertIn("user rejected MCP tool call", md)
        self.assertIn("credentials_missing", json.dumps(mesh_summary_payload(report), ensure_ascii=False))

    def test_inventory_and_actions_csv_include_required_columns(self) -> None:
        root = make_tmp()
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git")
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=fake, fetch=False)
        inventory = mesh_inventory_csv(report)
        actions = mesh_actions_csv(report)
        self.assertIn("nome_declarado,nome_esperado,repositorio_gitea", inventory)
        self.assertIn("ahead_behind", inventory)
        self.assertIn("action_id,nome_declarado,ambiente,tipo", actions)

    def test_automation_markdown_documents_five_minute_safe_cycle(self) -> None:
        root = make_tmp()
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, targets=(target,), environments=(env,), runner=FakeGit(), fetch=False)
        text = automation_markdown(report)
        self.assertIn("5 minutos", text)
        self.assertIn("New-ScheduledTaskAction", text)
        self.assertIn("*/5 * * * *", text)
        self.assertIn("nunca executar reset", text.lower())

    def test_orders_payload_maps_central_active_orders(self) -> None:
        root = make_tmp()
        central_root = root / "central" / "projects"
        order_dir = central_root / "01_alpha" / "orders" / "executivas"
        order_dir.mkdir(parents=True)
        (order_dir / "0001_EXECUTIVA__teste.md").write_text("# teste\n", encoding="utf-8")
        repo = make_repo(root, "alpha")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git")
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")
        report = build_mesh_report(root, central_root=central_root, targets=(target,), environments=(env,), runner=fake)
        payload = mesh_orders_payload(report)
        self.assertEqual(payload["repositories"][0]["centralFolder"], "01_alpha")
        self.assertEqual(len(payload["repositories"][0]["activeOrders"]), 1)

    def test_run_repository_mesh_writes_all_artifacts(self) -> None:
        tmp = make_tmp()
        ecosystem = tmp / "eco"
        project = tmp / "human"
        central = tmp / "central" / "projects" / "15_repo_tudo-para-ia-mais-humana-platform"
        ecosystem.mkdir()
        project.mkdir()
        central.mkdir(parents=True)
        repo = make_repo(ecosystem, "alpha")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git")
        target = RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")
        env = MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(ecosystem), "test")
        report = build_mesh_report(ecosystem, targets=(target,), environments=(env,), runner=fake)
        self.assertFalse(validate_report(report))
        records = repository_mesh_artifact_records(project, central)
        self.assertTrue(any("repository-mesh-inventory.json" in item["path"] for item in records))
        report, written = run_repository_mesh(
            ecosystem,
            project,
            central_platform_folder=central,
            fetch=False,
            plugin_auth_attempt="user rejected MCP tool call",
            runner=fake,
        )
        self.assertTrue((project / "dados" / "repository-mesh-inventory.json").exists())
        self.assertTrue((project / "matrizes" / "repository-mesh-actions.csv").exists())
        self.assertTrue((project / "ecossistema" / "REPOSITORY-MESH-SYNC.md").exists())
        self.assertTrue((central / "reports" / "EXECUTADO__repository-mesh-sync.md").exists())
        self.assertGreaterEqual(len(written), 8)

    def test_run_repository_mesh_records_central_write_failure_without_aborting_project_outputs(self) -> None:
        tmp = make_tmp()
        ecosystem = tmp / "eco"
        project = tmp / "human"
        central = tmp / "central" / "projects" / "15_repo_tudo-para-ia-mais-humana-platform"
        ecosystem.mkdir()
        project.mkdir()
        central.mkdir(parents=True)
        (central / "reports").write_text("not a directory\n", encoding="utf-8")
        repo = make_repo(ecosystem, "alpha")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git")
        report, written = run_repository_mesh(
            ecosystem,
            project,
            central_platform_folder=central,
            fetch=False,
            plugin_auth_attempt="user rejected MCP tool call",
            runner=fake,
        )
        self.assertGreater(len(report.targets), 0)
        self.assertTrue((project / "dados" / "repository-mesh-inventory.json").exists())
        self.assertTrue((project / "ecossistema" / "REPOSITORY-MESH-CENTRAL-WRITE-STATUS.md").exists())
        status = json.loads((project / "dados" / "repository-mesh-central-write-status.json").read_text(encoding="utf-8"))
        self.assertFalse(status["ok"])
        self.assertEqual(status["failureCount"], 1)
        self.assertFalse(any("EXECUTADO__repository-mesh-sync.md" in item.path for item in written))

    def test_cli_repo_mesh_writes_payload(self) -> None:
        tmp = make_tmp()
        ecosystem = tmp / "eco"
        project = tmp / "human"
        central = tmp / "central" / "projects" / "15_repo_tudo-para-ia-mais-humana-platform"
        ecosystem.mkdir()
        project.mkdir()
        central.mkdir(parents=True)
        code = main(
            [
                "repo-mesh",
                "--ecosystem-root",
                str(ecosystem),
                "--project-root",
                str(project),
                "--central-platform-folder",
                str(central),
                "--plugin-auth-attempt",
                "user rejected MCP tool call",
            ]
        )
        self.assertEqual(code, 0)
        summary = json.loads((project / "dados" / "repository-mesh-summary.json").read_text(encoding="utf-8"))
        self.assertEqual(summary["pluginAuthAttempt"], "user rejected MCP tool call")
        self.assertEqual(summary["targets"], len(default_repository_targets()))

    def test_cli_repo_mesh_falls_back_when_central_write_probe_fails(self) -> None:
        tmp = make_tmp()
        ecosystem = tmp / "eco"
        project = tmp / "human"
        central = tmp / "central" / "projects" / "15_repo_tudo-para-ia-mais-humana-platform"
        ecosystem.mkdir()
        project.mkdir()
        central.mkdir(parents=True)
        (central / "reports").write_text("not a directory\n", encoding="utf-8")
        code = main(
            [
                "repo-mesh",
                "--ecosystem-root",
                str(ecosystem),
                "--project-root",
                str(project),
                "--central-platform-folder",
                str(central),
                "--plugin-auth-attempt",
                "mcp_tool_call_rejected_before_router",
            ]
        )
        self.assertEqual(code, 0)
        status = json.loads((project / "dados" / "repository-mesh-central-write-status.json").read_text(encoding="utf-8"))
        self.assertIn("error", status)
        self.assertTrue(status["error"])
        self.assertEqual(status["used"], "")
        payload = json.loads((project / "dados" / "repository-mesh-readiness.json").read_text(encoding="utf-8"))
        self.assertIn("status", payload)


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