"""Tests for complyform.core.remediation.github_pr."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from complyform.core.errors import (
    PRBranchExistsError,
    PRCreationError,
    PRRepoDetectionError,
    PRTokenInvalidError,
)
from complyform.core.remediation.github_pr import (
    GitHubPRClient,
    PatchFile,
    PRResult,
    _parse_github_url,
    build_commit_message,
    build_pr_body,
    default_branch_name,
    detect_github_repo,
)

# ---- PatchFile / PRResult models ----


class TestPatchFile:
    def test_valid(self) -> None:
        pf = PatchFile(path="patches/foo.tf", content="resource {}")
        assert pf.path == "patches/foo.tf"
        assert pf.content == "resource {}"


class TestPRResult:
    def test_valid(self) -> None:
        pr = PRResult(
            url="https://github.com/o/r/pull/1",
            branch="complyform/cis-123",
            base="main",
            draft=False,
            files_committed=3,
            commit_sha="abc123",
        )
        assert pr.url == "https://github.com/o/r/pull/1"
        assert pr.files_committed == 3


# ---- URL parsing ----


class TestParseGitHubUrl:
    def test_https(self) -> None:
        owner, repo = _parse_github_url("https://github.com/acme/infra.git")
        assert owner == "acme"
        assert repo == "infra"

    def test_https_no_git(self) -> None:
        owner, repo = _parse_github_url("https://github.com/acme/infra")
        assert owner == "acme"
        assert repo == "infra"

    def test_ssh(self) -> None:
        owner, repo = _parse_github_url("git@github.com:acme/infra.git")
        assert owner == "acme"
        assert repo == "infra"

    def test_ssh_no_git(self) -> None:
        owner, repo = _parse_github_url("git@github.com:acme/infra")
        assert owner == "acme"
        assert repo == "infra"

    def test_invalid_url(self) -> None:
        with pytest.raises(PRRepoDetectionError):
            _parse_github_url("https://gitlab.com/o/r")


# ---- detect_github_repo ----


class TestDetectGithubRepo:
    def test_detect(self, tmp_path: object) -> None:
        import os
        from pathlib import Path

        p = Path(str(tmp_path))
        git_dir = p / ".git"
        git_dir.mkdir()
        config = git_dir / "config"
        config.write_text(
            '[remote "origin"]\n    url = https://github.com/acme/infra.git\n',
            encoding="utf-8",
        )
        orig = os.getcwd()
        try:
            os.chdir(str(p))
            owner, repo = detect_github_repo()
            assert owner == "acme"
            assert repo == "infra"
        finally:
            os.chdir(orig)

    def test_no_git(self, tmp_path: object) -> None:
        import os
        from pathlib import Path

        p = Path(str(tmp_path))
        orig = os.getcwd()
        try:
            os.chdir(str(p))
            with pytest.raises(PRRepoDetectionError):
                detect_github_repo()
        finally:
            os.chdir(orig)


# ---- Helpers ----


class TestDefaultBranchName:
    def test_format(self) -> None:
        name = default_branch_name("cis-gcp")
        assert name.startswith("complyform/cis-gcp-")


class TestBuildCommitMessage:
    def test_message(self) -> None:
        msg = build_commit_message("cis-gcp", 5)
        assert "cis-gcp" in msg
        assert "5 controls" in msg


class TestBuildPrBody:
    def test_body_contains_fields(self) -> None:
        body = build_pr_body(
            framework="cis-gcp",
            control_count=3,
            cloud="gcp",
            patches=[
                ("patches/a.tf", "CIS-1.1", "addr_a"),
            ],
            version="0.1.0",
        )
        assert "cis-gcp" in body
        assert "3" in body
        assert "gcp" in body
        assert "patches/a.tf" in body


# ---- GitHubPRClient ----


class TestGitHubPRClient:
    def _make_client(self) -> GitHubPRClient:
        return GitHubPRClient("ghp_test", "acme", "infra")

    @patch("httpx.Client.get")
    def test_validate_token_ok(self, mock_get: MagicMock) -> None:
        mock_get.return_value = MagicMock(
            status_code=200, raise_for_status=lambda: None
        )
        client = self._make_client()
        client.validate_token()

    @patch("httpx.Client.get")
    def test_validate_token_401(self, mock_get: MagicMock) -> None:
        mock_get.return_value = MagicMock(
            status_code=401,
            text="Unauthorized",
        )
        client = self._make_client()
        with pytest.raises(PRTokenInvalidError):
            client.validate_token()

    @patch("httpx.Client.get")
    def test_get_default_branch(self, mock_get: MagicMock) -> None:
        mock_get.return_value = MagicMock(
            status_code=200,
            json=lambda: {"default_branch": "main"},
            raise_for_status=lambda: None,
        )
        client = self._make_client()
        assert client.get_default_branch() == "main"

    @patch("httpx.Client.get")
    def test_get_default_branch_404(self, mock_get: MagicMock) -> None:
        mock_get.return_value = MagicMock(status_code=404)
        client = self._make_client()
        with pytest.raises(PRRepoDetectionError):
            client.get_default_branch()

    @patch("httpx.Client.post")
    def test_create_branch_422(self, mock_post: MagicMock) -> None:
        mock_post.return_value = MagicMock(status_code=422)
        client = self._make_client()
        with pytest.raises(PRBranchExistsError):
            client.create_branch("my-branch", "abc123")

    @patch("httpx.Client.post")
    def test_create_pull_request_ok(self, mock_post: MagicMock) -> None:
        mock_post.return_value = MagicMock(
            status_code=201,
            json=lambda: {
                "html_url": "https://github.com/o/r/pull/1",
                "head": {"sha": "abc123"},
            },
        )
        client = self._make_client()
        result = client.create_pull_request(
            title="test",
            body="body",
            head="branch",
            base="main",
        )
        assert isinstance(result, PRResult)
        assert result.url == "https://github.com/o/r/pull/1"

    @patch("httpx.Client.post")
    def test_create_pull_request_fail(self, mock_post: MagicMock) -> None:
        mock_post.return_value = MagicMock(
            status_code=403,
            text="Forbidden",
        )
        client = self._make_client()
        with pytest.raises(PRCreationError):
            client.create_pull_request(
                title="test",
                body="body",
                head="branch",
                base="main",
            )

    @patch("httpx.Client.post")
    def test_create_tree(self, mock_post: MagicMock) -> None:
        mock_post.return_value = MagicMock(
            status_code=201,
            json=lambda: {"sha": "tree_sha"},
            raise_for_status=lambda: None,
        )
        client = self._make_client()
        sha = client.create_tree(
            "base_sha",
            [PatchFile(path="a.tf", content="x")],
        )
        assert sha == "tree_sha"

    @patch("httpx.Client.post")
    def test_create_commit(self, mock_post: MagicMock) -> None:
        mock_post.return_value = MagicMock(
            status_code=201,
            json=lambda: {"sha": "commit_sha"},
            raise_for_status=lambda: None,
        )
        client = self._make_client()
        sha = client.create_commit("tree", "parent", "msg")
        assert sha == "commit_sha"
