import json
import os
import shutil
import subprocess
import tempfile
import textwrap
import unittest
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / ".gitea" / "ci" / "thirdparty_skills.json"
WORKFLOW = ROOT / ".gitea" / "workflows" / "update-thirdparty-skills.yml"
TSL_SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml"
LEGACY_WORKFLOW = ROOT / ".gitea" / "workflows" / "update-thirdparty-superpowers.yml"
UPDATE_SCRIPT = ROOT / ".gitea" / "ci" / "update_thirdparty_skills.sh"
SYNC_SCRIPT = ROOT / ".gitea" / "ci" / "sync_thirdparty_skills.sh"
SKILLS_MD = ROOT / "SKILLS.md"
SUPERPOWERS_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "superpowers.list"
UI_UX_PRO_MAX_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "ui-ux-pro-max.list"
UI_UX_PRO_MAX_DIR = ROOT / "skills" / "thirdparty" / "ui-ux-pro-max"
BROOKS_LINT_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "brooks-lint.list"
CODEBASE_RECON_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "codebase-recon.list"
PATHFINDING_DIR = ROOT / "skills" / "thirdparty" / "pathfinding"


def load_manifest() -> dict:
    return json.loads(MANIFEST.read_text(encoding="utf-8"))


def bash_path(path: Path) -> str:
    resolved = path.resolve()
    if os.name != "nt":
        return resolved.as_posix()
    drive = resolved.drive.rstrip(":").lower()
    rest = resolved.as_posix()[2:]
    return f"/mnt/{drive}{rest}"


def run_command(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        list(args),
        cwd=cwd,
        capture_output=True,
        text=True,
    )


def extract_workflow_region(name: str) -> str:
    text = WORKFLOW.read_text(encoding="utf-8")
    begin = f"# BEGIN {name}"
    end = f"# END {name}"
    if begin not in text or end not in text:
        raise AssertionError(f"workflow region markers not found: {name}")
    body = text.split(begin, 1)[1].split(end, 1)[0]
    return textwrap.dedent(body).strip() + "\n"


class ThirdpartySkillsPipelineTests(unittest.TestCase):
    def test_manifest_declares_all_thirdparty_sources(self):
        data = load_manifest()
        self.assertEqual(
            [entry["id"] for entry in data["sources"]],
            [
                "superpowers",
                "ui-ux-pro-max",
                "andrej-karpathy-skills",
                "brooks-lint",
                "codebase-recon",
                "codebase-migrate",
                "uncle-bob-craft",
            ],
        )

    def test_karpathy_manifest_uses_copy_skill_dirs_sync_mode(self):
        data = load_manifest()
        karpathy = next(
            item for item in data["sources"] if item["id"] == "andrej-karpathy-skills"
        )
        self.assertEqual(karpathy["sync_mode"], "copy_skill_dirs")
        self.assertEqual(karpathy["snapshot_dir"], "andrej-karpathy-skills")
        self.assertEqual(karpathy["skills_subdir"], "skills")
        self.assertEqual(
            karpathy["source_list"], "skills/thirdparty/.sources/andrej-karpathy-skills.list"
        )

    def test_ui_ux_pro_max_uses_render_skill_sync_mode(self):
        data = load_manifest()
        ui_skill = next(item for item in data["sources"] if item["id"] == "ui-ux-pro-max")
        self.assertEqual(ui_skill["sync_mode"], "render_skill")
        self.assertEqual(ui_skill["snapshot_dir"], "ui-ux-pro-max")

    def test_architecture_skill_sources_use_include_filters(self):
        data = load_manifest()
        brooks = next(item for item in data["sources"] if item["id"] == "brooks-lint")
        self.assertEqual(brooks["sync_mode"], "copy_skill_dirs")
        self.assertEqual(brooks["snapshot_dir"], "brooks-lint")
        self.assertEqual(brooks["skills_subdir"], "skills")
        self.assertEqual(
            brooks["source_list"], "skills/thirdparty/.sources/brooks-lint.list"
        )
        self.assertIn("brooks-audit", brooks["include_skill_dirs"])
        self.assertIn("brooks-review", brooks["include_skill_dirs"])
        self.assertIn("_shared", brooks["include_skill_dirs"])

        recon = next(item for item in data["sources"] if item["id"] == "codebase-recon")
        self.assertEqual(recon["sync_mode"], "copy_skill_dirs")
        self.assertEqual(recon["snapshot_dir"], "outfitter-agents")
        self.assertEqual(recon["skills_subdir"], "plugins/outfitter/skills")
        self.assertEqual(
            recon["source_list"], "skills/thirdparty/.sources/codebase-recon.list"
        )
        self.assertEqual(recon["include_skill_dirs"], ["codebase-recon", "pathfinding"])

        migrate = next(
            item for item in data["sources"] if item["id"] == "codebase-migrate"
        )
        self.assertEqual(
            migrate["upstream_repo"],
            "https://github.com/ComposioHQ/awesome-codex-skills.git",
        )
        self.assertEqual(migrate["upstream_ref"], "master")
        self.assertEqual(migrate["sync_mode"], "copy_skill_dirs")
        self.assertEqual(migrate["snapshot_dir"], "awesome-codex-skills")
        self.assertEqual(migrate["skills_subdir"], ".")
        self.assertEqual(
            migrate["source_list"], "skills/thirdparty/.sources/codebase-migrate.list"
        )
        self.assertEqual(migrate["include_skill_dirs"], ["codebase-migrate"])

        craft = next(
            item for item in data["sources"] if item["id"] == "uncle-bob-craft"
        )
        self.assertEqual(
            craft["upstream_repo"],
            "https://github.com/sickn33/antigravity-awesome-skills.git",
        )
        self.assertEqual(craft["upstream_ref"], "main")
        self.assertEqual(craft["sync_mode"], "copy_skill_dirs")
        self.assertEqual(craft["snapshot_dir"], "antigravity-awesome-skills")
        self.assertEqual(craft["skills_subdir"], "skills")
        self.assertEqual(
            craft["source_list"], "skills/thirdparty/.sources/uncle-bob-craft.list"
        )
        self.assertEqual(craft["include_skill_dirs"], ["uncle-bob-craft"])

    def test_superpowers_manifest_prunes_non_superpowers_paths(self):
        data = load_manifest()
        superpowers = next(item for item in data["sources"] if item["id"] == "superpowers")
        self.assertEqual(superpowers["remove_paths"], ["skills/ui-ux-pro-max"])

    def test_workflow_inlines_update_and_sync_in_single_serial_job(self):
        text = WORKFLOW.read_text(encoding="utf-8")
        self.assertFalse(LEGACY_WORKFLOW.exists())
        self.assertFalse(UPDATE_SCRIPT.exists())
        self.assertFalse(SYNC_SCRIPT.exists())
        self.assertIn("update_and_sync:", text)
        self.assertNotIn("\n  update:\n", text)
        self.assertNotIn("\n  sync:\n", text)
        self.assertIn("# BEGIN update_thirdparty_snapshots", text)
        self.assertIn("# BEGIN sync_thirdparty_skills", text)
        self.assertNotIn("update_thirdparty_skills.sh", text)
        self.assertNotIn("sync_thirdparty_skills.sh", text)
        self.assertNotIn("git merge", text)
        self.assertNotIn("git pull", text)

    def test_workflow_has_serial_concurrency_and_literal_generic_paths(self):
        text = WORKFLOW.read_text(encoding="utf-8")
        self.assertIn("concurrency:", text)
        self.assertIn("update-thirdparty-${{ github.repository }}", text)
        self.assertIn('MANIFEST_PATH: ".gitea/ci/thirdparty_skills.json"', text)
        self.assertIn("update_thirdparty_snapshots", text)
        self.assertIn("sync_thirdparty_skills", text)

    def test_inline_workflow_exposes_manifest_and_publish_contract(self):
        text = WORKFLOW.read_text(encoding="utf-8")
        self.assertIn('MANIFEST_PATH="${MANIFEST_PATH:-.gitea/ci/thirdparty_skills.json}"', text)
        self.assertIn('TARGET_BRANCH="${TARGET_BRANCH:-thirdparty/skill}"', text)
        self.assertIn('TARGET_BRANCH="${TARGET_BRANCH:-main}"', text)
        self.assertIn(':package: deps(thirdparty): update snapshots', text)
        self.assertIn(':package: deps(skills): sync thirdparty skills', text)
        self.assertIn('git push origin "$TARGET_BRANCH"', text)

    def test_ci_committers_share_explicit_git_identity(self):
        workflow_text = TSL_SYNC_WORKFLOW.read_text(encoding="utf-8")
        thirdparty_text = WORKFLOW.read_text(encoding="utf-8")

        for text in (workflow_text, thirdparty_text):
            self.assertIn('GIT_USER_NAME: "ci[bot]"', text)
            self.assertIn('GIT_USER_EMAIL: "ci[bot]@tinysoft.com.cn"', text)
            self.assertIn('git config user.name "$GIT_USER_NAME"', text)
            self.assertIn('git config user.email "$GIT_USER_EMAIL"', text)
            self.assertNotIn("COMMIT_AUTHOR_NAME", text)
            self.assertNotIn("COMMIT_AUTHOR_EMAIL", text)
            self.assertNotIn("@local", text)

    def test_skills_doc_points_to_generic_thirdparty_sources(self):
        text = SKILLS_MD.read_text(encoding="utf-8")
        # Check that third-party skills section exists (without enforcing exact heading format)
        self.assertIn("thirdparty", text.lower())
        self.assertIn("skills/thirdparty/", text)
        self.assertNotIn("Third-party Skills (superpowers)", text)

    def test_superpowers_and_ui_ux_pro_max_source_lists_exist(self):
        self.assertTrue(SUPERPOWERS_LIST.is_file())
        self.assertTrue(UI_UX_PRO_MAX_LIST.is_file())
        self.assertTrue(CODEBASE_RECON_LIST.is_file())
        self.assertIn("using-superpowers", SUPERPOWERS_LIST.read_text(encoding="utf-8"))
        self.assertIn("ui-ux-pro-max", UI_UX_PRO_MAX_LIST.read_text(encoding="utf-8"))
        self.assertIn("pathfinding", CODEBASE_RECON_LIST.read_text(encoding="utf-8"))

    def test_codebase_recon_pathfinding_dependency_is_synced(self):
        self.assertTrue((PATHFINDING_DIR / "SKILL.md").is_file())
        self.assertTrue(
            (PATHFINDING_DIR / "references" / "confidence.md").is_file()
        )

    def test_ui_ux_pro_max_output_exists_with_data_and_scripts(self):
        self.assertTrue((UI_UX_PRO_MAX_DIR / "SKILL.md").is_file())
        self.assertTrue((UI_UX_PRO_MAX_DIR / "data").is_dir())
        self.assertTrue((UI_UX_PRO_MAX_DIR / "scripts").is_dir())

    def test_inline_update_materializes_manifest_before_target_checkout(self):
        text = WORKFLOW.read_text(encoding="utf-8")
        self.assertIn('manifest_copy="$tmp_dir/thirdparty_skills.json"', text)
        self.assertIn('cp "$MANIFEST_PATH" "$manifest_copy"', text)
        self.assertIn('MANIFEST_PATH="$manifest_copy"', text)
        self.assertIn("remove_paths", text)
        self.assertIn('remove_snapshot_paths "$snapshot_dir" "$remove_paths"', text)
        self.assertIn("- Remove-Paths:", text)
        self.assertIn('if ! emit_sources_tsv > "$sources_file"; then', text)
        self.assertNotIn("done < <(emit_sources_tsv)", text)
        self.assertLess(
            text.index('cp "$MANIFEST_PATH" "$manifest_copy"'),
            text.index('git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH"'),
        )

    def test_inline_sync_assumes_thirdparty_snapshot_is_already_clean(self):
        text = WORKFLOW.read_text(encoding="utf-8")
        self.assertIn('"\\x1f".join(', text)
        self.assertIn("while IFS=$'\\x1f' read -r", text)
        self.assertIn("include_skill_dirs", text)
        self.assertIn('skill_dir_included "$name" "$include_skill_dirs"', text)
        self.assertNotIn("while IFS=$'\\t' read -r", text)
        self.assertNotIn("exclude_skill_dirs", text)
        self.assertNotIn("is_excluded_skill_dir", text)

    def test_inline_sync_generates_karpathy_outputs_in_temp_repo(self):
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_root = Path(tmp_dir)
            mirror = tmp_root / "origin.git"
            work = tmp_root / "work"

            clone_mirror = run_command("git", "clone", "--mirror", str(ROOT), str(mirror))
            self.assertEqual(clone_mirror.returncode, 0, msg=clone_mirror.stderr)

            main_ref = run_command(
                "git",
                f"--git-dir={mirror}",
                "rev-parse",
                "refs/remotes/origin/main",
            )
            self.assertEqual(main_ref.returncode, 0, msg=main_ref.stderr)
            expose_main_branch = run_command(
                "git",
                f"--git-dir={mirror}",
                "update-ref",
                "refs/heads/main",
                main_ref.stdout.strip(),
            )
            self.assertEqual(
                expose_main_branch.returncode,
                0,
                msg=expose_main_branch.stderr,
            )

            thirdparty_ref = run_command(
                "git",
                f"--git-dir={mirror}",
                "rev-parse",
                "refs/remotes/origin/thirdparty/skill",
            )
            self.assertEqual(thirdparty_ref.returncode, 0, msg=thirdparty_ref.stderr)
            expose_thirdparty_branch = run_command(
                "git",
                f"--git-dir={mirror}",
                "update-ref",
                "refs/heads/thirdparty/skill",
                thirdparty_ref.stdout.strip(),
            )
            self.assertEqual(
                expose_thirdparty_branch.returncode,
                0,
                msg=expose_thirdparty_branch.stderr,
            )

            clone_work = run_command("git", "clone", str(mirror), str(work))
            self.assertEqual(clone_work.returncode, 0, msg=clone_work.stderr)

            set_remote = run_command(
                "git", "-C", str(work), "remote", "set-url", "origin", bash_path(mirror)
            )
            self.assertEqual(set_remote.returncode, 0, msg=set_remote.stderr)

            manifest_data = load_manifest()
            manifest_data["sources"] = [
                entry
                for entry in manifest_data["sources"]
                if entry["id"] == "andrej-karpathy-skills"
            ]
            (work / ".gitea" / "ci" / "thirdparty_skills.json").write_text(
                json.dumps(manifest_data, indent=2) + "\n",
                encoding="utf-8",
            )
            sync_script = extract_workflow_region("sync_thirdparty_skills")
            script_path = work / ".sync-thirdparty-test.sh"
            script_path.write_text(
                'export GIT_USER_NAME="test"\n'
                'export GIT_USER_EMAIL="test@example.invalid"\n'
                + sync_script,
                encoding="utf-8",
                newline="\n",
            )
            env = os.environ.copy()
            env.update(
                {
                    "REPO_DIR": str(work),
                    "THIRDPARTY_BRANCH": "thirdparty/skill",
                    "MANIFEST_PATH": ".gitea/ci/thirdparty_skills.json",
                }
            )
            sync_result = subprocess.run(
                ["bash", script_path.name],
                cwd=work,
                env=env,
                capture_output=True,
                text=True,
            )
            self.assertEqual(
                sync_result.returncode,
                0,
                msg=sync_result.stdout + sync_result.stderr,
            )

            generated_list = (
                work / "skills" / "thirdparty" / ".sources" / "andrej-karpathy-skills.list"
            )
            generated_skill = (
                work / "skills" / "thirdparty" / "karpathy-guidelines" / "SKILL.md"
            )
            self.assertTrue(generated_list.is_file())
            self.assertTrue(generated_skill.is_file())
            self.assertIn(
                "karpathy-guidelines", generated_list.read_text(encoding="utf-8")
            )


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