from __future__ import annotations

import json
import os
import unittest
from datetime import datetime, timezone
from pathlib import Path

from mais_humana.repository_mesh import MeshEnvironment, MeshEnvironmentKind, RepositoryTarget, build_mesh_report
from mais_humana.repository_mesh_reconciliation import build_reconciliation_plan
from mais_humana.repository_mesh_runtime import (
    RuntimeCommandStatus,
    RuntimeLockStatus,
    acquire_lock,
    build_runtime_cycle,
    commands_from_report_and_plan,
    cron_scheduler_spec,
    lock_is_stale,
    release_lock,
    runtime_csv,
    runtime_jsonl,
    runtime_markdown,
    scheduler_markdown,
    scheduler_payload,
    windows_scheduler_spec,
    write_runtime_artifacts,
)
from tests.helpers import make_tmp
from tests.test_repository_mesh import FakeGit, make_repo


def target() -> RepositoryTarget:
    return RepositoryTarget("alpha", "admin/alpha", "alpha", "01_alpha")


def env(root: Path) -> MeshEnvironment:
    return MeshEnvironment("primary", MeshEnvironmentKind.WINDOWS_PRIMARY, str(root), "test")


def clean_report(root: Path):
    repo = make_repo(root, "alpha")
    fake = FakeGit()
    fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git")
    return build_mesh_report(root, targets=(target(),), environments=(env(root),), runner=fake)


class RepositoryMeshRuntimeTests(unittest.TestCase):
    def test_lock_acquire_busy_release_cycle(self) -> None:
        tmp = make_tmp()
        lock_path = tmp / "mesh.lock.json"
        first = acquire_lock(lock_path, owner="one", expires_after_seconds=600)
        self.assertEqual(first.status, RuntimeLockStatus.ACQUIRED)
        second = acquire_lock(lock_path, owner="two", expires_after_seconds=600)
        self.assertEqual(second.status, RuntimeLockStatus.BUSY)
        released = release_lock(first)
        self.assertEqual(released.status, RuntimeLockStatus.RELEASED)
        third = acquire_lock(lock_path, owner="three", expires_after_seconds=600)
        self.assertEqual(third.status, RuntimeLockStatus.ACQUIRED)
        release_lock(third)

    def test_lock_stale_detection_accepts_malformed_payload_as_recoverable(self) -> None:
        self.assertTrue(lock_is_stale({"createdAt": "bad", "expiresAfterSeconds": 600}))
        self.assertTrue(lock_is_stale({"createdAt": "2026-01-01T00:00:00+00:00", "expiresAfterSeconds": 0}))
        self.assertTrue(
            lock_is_stale(
                {
                    "createdAt": datetime.now(timezone.utc).isoformat(),
                    "expiresAfterSeconds": 600,
                    "pid": 99999999 if os.name == "nt" else 999999,
                }
            )
        )

    def test_commands_from_clean_report_allow_only_safe_fetch_like_commands(self) -> None:
        tmp = make_tmp()
        report = clean_report(tmp)
        plan = build_reconciliation_plan(report)
        commands = commands_from_report_and_plan(report, plan)
        self.assertTrue(commands)
        self.assertTrue(any(command.status == RuntimeCommandStatus.ALLOWED for command in commands))
        self.assertFalse(any("reset" in command.command for command in commands))

    def test_dirty_report_blocks_runtime_commands(self) -> None:
        tmp = make_tmp()
        repo = make_repo(tmp, "alpha")
        fake = FakeGit()
        fake.set_repo(repo, remote="https://git.ami.app.br/admin/alpha.git", status=(" M README.md",))
        report = build_mesh_report(tmp, targets=(target(),), environments=(env(tmp),), runner=fake)
        plan = build_reconciliation_plan(report)
        commands = commands_from_report_and_plan(report, plan)
        self.assertTrue(any(command.status == RuntimeCommandStatus.BLOCKED for command in commands))
        self.assertTrue(any("destrutivo" in " ".join(command.blocked_reasons) or "manual" in " ".join(command.blocked_reasons) for command in commands))

    def test_runtime_cycle_blocks_everything_when_lock_not_acquired(self) -> None:
        tmp = make_tmp()
        report = clean_report(tmp)
        plan = build_reconciliation_plan(report)
        first = acquire_lock(tmp / "lock.json", owner="one")
        busy = acquire_lock(tmp / "lock.json", owner="two")
        cycle = build_runtime_cycle(report, plan, lock=busy)
        self.assertEqual(cycle.lock.status, RuntimeLockStatus.BUSY)
        self.assertTrue(all(result.status == RuntimeCommandStatus.BLOCKED for result in cycle.results))
        release_lock(first)

    def test_runtime_outputs_csv_jsonl_and_markdown(self) -> None:
        tmp = make_tmp()
        report = clean_report(tmp)
        plan = build_reconciliation_plan(report)
        lock = acquire_lock(tmp / "lock.json", owner="one")
        cycle = build_runtime_cycle(report, plan, lock=lock)
        self.assertIn("command_id,target_name,status", runtime_csv(cycle))
        self.assertIn("Repository Mesh Runtime Cycle", runtime_markdown(cycle))
        first_line = runtime_jsonl(cycle).splitlines()[0]
        self.assertIn("command", json.loads(first_line))
        release_lock(lock)

    def test_scheduler_specs_include_five_minute_commands(self) -> None:
        tmp = make_tmp()
        win = windows_scheduler_spec(
            python_exe="python.exe",
            project_root=tmp,
            ecosystem_root=tmp / "eco",
            central_platform_folder=tmp / "central",
        )
        cron = cron_scheduler_spec(
            python_exe="python",
            project_root=tmp,
            ecosystem_root=tmp / "eco",
            central_platform_folder=tmp / "central",
        )
        payload = scheduler_payload((win, cron))
        md = scheduler_markdown((win, cron))
        self.assertIn("windows_task", payload["kinds"])
        self.assertIn("cron", payload["kinds"])
        self.assertIn("New-ScheduledTaskAction", md)
        self.assertIn("crontab", md)

    def test_write_runtime_artifacts_records_project_and_central_files(self) -> None:
        tmp = make_tmp()
        project = tmp / "project"
        central = tmp / "central"
        project.mkdir()
        central.mkdir()
        report = clean_report(tmp / "eco")
        plan = build_reconciliation_plan(report)
        lock = acquire_lock(project / "dados" / "lock.json", owner="one")
        cycle = build_runtime_cycle(report, plan, lock=lock)
        specs = (
            windows_scheduler_spec(python_exe="python.exe", project_root=project, ecosystem_root=tmp / "eco", central_platform_folder=central),
            cron_scheduler_spec(python_exe="python", project_root=project, ecosystem_root=tmp / "eco", central_platform_folder=central),
        )
        records = write_runtime_artifacts(cycle, specs, project, central_platform_folder=central)
        self.assertTrue((project / "dados" / "repository-mesh-runtime-cycle.json").exists())
        self.assertTrue((project / "dados" / "repository-mesh-runtime.jsonl").exists())
        self.assertTrue((project / "ecossistema" / "REPOSITORY-MESH-SCHEDULERS.md").exists())
        self.assertTrue((central / "reports" / "EXECUTADO__repository-mesh-runtime.md").exists())
        self.assertGreaterEqual(len(records), 6)
        release_lock(lock)


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