import argparse
import json
from pathlib import Path

from cihub.commands.docs import _check_internal_links, _run_lychee, cmd_docs
from cihub.types import CommandResult


def test_docs_generate_writes_files(tmp_path: Path) -> None:
    args = argparse.Namespace(
        subcommand="generate",
        output=str(tmp_path),
        check=False,
        json=True,
    )
    result = cmd_docs(args)
    assert isinstance(result, CommandResult)
    assert result.exit_code == 0

    cli_path = tmp_path / "CLI.md"
    config_path = tmp_path / "CONFIG.md"
    assert cli_path.exists()
    assert config_path.exists()
    assert "Generated by `cihub docs generate`" in cli_path.read_text(encoding="utf-8")


def test_docs_check_ok(tmp_path: Path) -> None:
    generate_args = argparse.Namespace(
        subcommand="generate",
        output=str(tmp_path),
        check=False,
        json=True,
    )
    cmd_docs(generate_args)

    check_args = argparse.Namespace(
        subcommand="check",
        output=str(tmp_path),
        json=True,
    )
    result = cmd_docs(check_args)
    assert isinstance(result, CommandResult)
    assert result.exit_code == 0


def test_docs_check_detects_drift(tmp_path: Path) -> None:
    generate_args = argparse.Namespace(
        subcommand="generate",
        output=str(tmp_path),
        check=False,
        json=True,
    )
    cmd_docs(generate_args)

    (tmp_path / "CLI.md").write_text("corrupted", encoding="utf-8")

    check_args = argparse.Namespace(
        subcommand="check",
        output=str(tmp_path),
        json=True,
    )
    result = cmd_docs(check_args)
    assert isinstance(result, CommandResult)
    assert result.exit_code == 1


def test_internal_links_valid(tmp_path: Path) -> None:
    """Test that valid internal links pass."""
    (tmp_path / "README.md").write_text("See [guide](guide.md) for details.", encoding="utf-8")
    (tmp_path / "guide.md").write_text("# Guide", encoding="utf-8")

    problems = _check_internal_links(tmp_path)
    assert problems == []


def test_internal_links_broken(tmp_path: Path) -> None:
    """Test that broken internal links are detected."""
    (tmp_path / "README.md").write_text("See [missing](nonexistent.md) for details.", encoding="utf-8")

    problems = _check_internal_links(tmp_path)
    assert len(problems) == 1
    assert "nonexistent.md" in problems[0]["target"]
    assert problems[0]["code"] == "CIHUB-DOCS-BROKEN-LINK"


def test_internal_links_skips_external(tmp_path: Path) -> None:
    """Test that external links are skipped."""
    (tmp_path / "README.md").write_text(
        "See [GitHub](https://github.com) and [anchor](#section).",
        encoding="utf-8",
    )

    problems = _check_internal_links(tmp_path)
    assert problems == []


def test_internal_links_skips_code_blocks(tmp_path: Path) -> None:
    """Links inside fenced code blocks should be ignored."""
    (tmp_path / "README.md").write_text(
        "```md\nSee [missing](missing.md)\n```\n",
        encoding="utf-8",
    )

    problems = _check_internal_links(tmp_path)
    assert problems == []


def test_internal_links_reference_definitions(tmp_path: Path) -> None:
    """Reference-style link definitions should be checked."""
    (tmp_path / "README.md").write_text(
        "See [Guide][guide].\n\n[guide]: guide.md\n",
        encoding="utf-8",
    )
    (tmp_path / "guide.md").write_text("# Guide", encoding="utf-8")

    problems = _check_internal_links(tmp_path)
    assert problems == []


def test_internal_links_reference_definitions_broken(tmp_path: Path) -> None:
    """Broken reference-style links should be detected."""
    (tmp_path / "README.md").write_text(
        "See [Guide][guide].\n\n[guide]: missing.md\n",
        encoding="utf-8",
    )

    problems = _check_internal_links(tmp_path)
    assert len(problems) == 1
    assert "missing.md" in problems[0]["target"]


def test_internal_links_skip_development_archive(tmp_path: Path) -> None:
    """Archived docs are excluded from link checking to avoid churn."""
    archive_dir = tmp_path / "development" / "archive"
    archive_dir.mkdir(parents=True)
    (archive_dir / "OLD.md").write_text("See [missing](missing.md).", encoding="utf-8")

    guides_dir = tmp_path / "guides"
    guides_dir.mkdir(parents=True)
    (guides_dir / "GUIDE.md").write_text("See [missing](missing.md).", encoding="utf-8")

    problems = _check_internal_links(tmp_path)
    assert len(problems) == 1
    assert problems[0]["file"].endswith("GUIDE.md")


def test_run_lychee_parses_json_errors(tmp_path: Path, monkeypatch) -> None:
    """Lychee JSON output is parsed into actionable per-link problems."""
    docs_dir = tmp_path / "docs"
    docs_dir.mkdir()
    (tmp_path / "README.md").write_text("# Root", encoding="utf-8")

    captured: dict[str, object] = {}

    class _Proc:
        def __init__(self) -> None:
            self.returncode = 2
            self.stdout = json.dumps(
                {
                    "error_map": {
                        "docs/guides/GETTING_STARTED.md": [
                            {
                                "url": "file:///missing.md",
                                "status": {"text": "Cannot find file", "details": "File not found"},
                            }
                        ]
                    }
                }
            )
            self.stderr = ""

    def fake_safe_run(cmd, *, cwd=None, timeout=None, **kwargs):  # noqa: ANN001 - test stub
        captured["cmd"] = list(cmd)
        captured["cwd"] = cwd
        captured["timeout"] = timeout
        return _Proc()

    monkeypatch.setattr("cihub.commands.docs.links.safe_run", fake_safe_run)

    exit_code, problems = _run_lychee(docs_dir, external=False)

    assert exit_code == 2
    assert len(problems) == 1
    assert problems[0]["file"] == "docs/guides/GETTING_STARTED.md"
    assert problems[0]["url"] == "file:///missing.md"
    assert problems[0]["status"] == "Cannot find file"
    assert problems[0]["details"] == "File not found"

    cmd = captured["cmd"]
    assert isinstance(cmd, list)
    assert cmd[:3] == ["lychee", "docs", "README.md"]
    assert "--offline" in cmd
    assert "--format" in cmd and "json" in cmd
    assert "--exclude-path" in cmd
