"""Tests for GitHubSarifUploader."""

import base64
import gzip
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from tools.github_upload.uploader import (
    GitHubSarifUploader,
    GitHubUploadResult,
    _validate_branch_name,
    _validate_commit_sha,
    _validate_github_name,
    _validate_path,
)


class TestGitHubUploadResult:
    """Tests for GitHubUploadResult dataclass."""

    def test_success_result_to_dict(self) -> None:
        result = GitHubUploadResult(
            success=True,
            sarif_id="12345",
            processing_url="https://api.github.com/processing/12345",
            security_tab_url="https://github.com/owner/repo/security/code-scanning",
        )
        d = result.to_dict()
        assert d["success"] is True
        assert d["sarif_id"] == "12345"
        assert d["security_tab_url"] == "https://github.com/owner/repo/security/code-scanning"
        assert d["error"] is None

    def test_error_result_to_dict(self) -> None:
        result = GitHubUploadResult(
            success=False,
            error="Permission denied",
            error_type="PermissionDenied",
        )
        d = result.to_dict()
        assert d["success"] is False
        assert d["error"] == "Permission denied"
        assert d["error_type"] == "PermissionDenied"


class TestValidationFunctions:
    """Tests for input validation functions."""

    def test_validate_github_name_valid(self) -> None:
        _validate_github_name("owner", "owner")
        _validate_github_name("my-repo", "repo")
        _validate_github_name("my_repo", "repo")
        _validate_github_name("my.repo", "repo")
        _validate_github_name("a1", "repo")

    def test_validate_github_name_empty(self) -> None:
        with pytest.raises(ValueError, match="must be 1-100 characters"):
            _validate_github_name("", "owner")

    def test_validate_github_name_too_long(self) -> None:
        with pytest.raises(ValueError, match="must be 1-100 characters"):
            _validate_github_name("a" * 101, "owner")

    def test_validate_github_name_invalid_chars(self) -> None:
        with pytest.raises(ValueError, match="contains invalid characters"):
            _validate_github_name("owner/repo", "owner")
        with pytest.raises(ValueError, match="contains invalid characters"):
            _validate_github_name("owner repo", "owner")
        with pytest.raises(ValueError, match="contains invalid characters"):
            _validate_github_name("-invalid", "owner")

    def test_validate_commit_sha_valid(self) -> None:
        _validate_commit_sha("a" * 40)
        _validate_commit_sha("1234567890abcdef1234567890abcdef12345678")

    def test_validate_commit_sha_invalid(self) -> None:
        with pytest.raises(ValueError, match="Invalid commit SHA"):
            _validate_commit_sha("short")
        with pytest.raises(ValueError, match="Invalid commit SHA"):
            _validate_commit_sha("g" * 40)  # 'g' is not hex

    def test_validate_branch_name_valid(self) -> None:
        _validate_branch_name("main")
        _validate_branch_name("feature/my-feature")
        _validate_branch_name("release-1.0.0")

    def test_validate_branch_name_empty(self) -> None:
        with pytest.raises(ValueError, match="must be 1-255 characters"):
            _validate_branch_name("")

    def test_validate_branch_name_too_long(self) -> None:
        with pytest.raises(ValueError, match="must be 1-255 characters"):
            _validate_branch_name("a" * 256)

    def test_validate_branch_name_double_dot(self) -> None:
        with pytest.raises(ValueError, match="cannot contain"):
            _validate_branch_name("feature..test")

    def test_validate_path_exists(self) -> None:
        with tempfile.TemporaryDirectory() as tmpdir:
            path = Path(tmpdir)
            resolved = _validate_path(path, must_exist=True)
            assert resolved.exists()

    def test_validate_path_not_exists(self) -> None:
        with pytest.raises(ValueError, match="does not exist"):
            _validate_path(Path("/nonexistent/path"), must_exist=True)


class TestGitHubSarifUploader:
    """Tests for GitHubSarifUploader class."""

    def test_parse_remote_url_ssh(self) -> None:
        uploader = GitHubSarifUploader()
        owner, repo = uploader._parse_remote_url("git@github.com:owner/repo.git")
        assert owner == "owner"
        assert repo == "repo"

    def test_parse_remote_url_https(self) -> None:
        uploader = GitHubSarifUploader()
        owner, repo = uploader._parse_remote_url("https://github.com/owner/repo.git")
        assert owner == "owner"
        assert repo == "repo"

    def test_parse_remote_url_https_no_git(self) -> None:
        uploader = GitHubSarifUploader()
        owner, repo = uploader._parse_remote_url("https://github.com/owner/repo")
        assert owner == "owner"
        assert repo == "repo"

    def test_parse_remote_url_invalid(self) -> None:
        uploader = GitHubSarifUploader()
        with pytest.raises(ValueError, match="Unsupported remote URL format"):
            uploader._parse_remote_url("https://gitlab.com/owner/repo.git")

    def test_encode_sarif(self) -> None:
        uploader = GitHubSarifUploader()
        with tempfile.NamedTemporaryFile(suffix=".sarif", delete=False) as f:
            content = '{"$schema": "https://sarif.schema.json"}'
            f.write(content.encode())
            f.flush()
            sarif_path = Path(f.name)

        try:
            encoded = uploader.encode_sarif(sarif_path)
            decoded = gzip.decompress(base64.b64decode(encoded))
            assert decoded.decode() == content
        finally:
            sarif_path.unlink()

    def test_encode_sarif_too_large(self) -> None:
        uploader = GitHubSarifUploader()
        with tempfile.NamedTemporaryFile(suffix=".sarif", delete=False) as f:
            f.write(b"x" * (11 * 1024 * 1024))  # 11 MB
            f.flush()
            sarif_path = Path(f.name)

        try:
            with pytest.raises(ValueError, match="SARIF file too large"):
                uploader.encode_sarif(sarif_path)
        finally:
            sarif_path.unlink()

    @patch("subprocess.run")
    def test_check_gh_installed_not_found(self, mock_run: MagicMock) -> None:
        mock_run.side_effect = FileNotFoundError()
        uploader = GitHubSarifUploader()
        error = uploader._check_gh_installed()
        assert "not installed" in error

    @patch("subprocess.run")
    def test_check_gh_installed_success(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        uploader = GitHubSarifUploader()
        error = uploader._check_gh_installed()
        assert error is None

    @patch("subprocess.run")
    def test_check_gh_authenticated_failure(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=1)
        uploader = GitHubSarifUploader()
        error = uploader._check_gh_authenticated()
        assert "Not logged in" in error

    @patch("subprocess.run")
    def test_check_gh_authenticated_success(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)
        uploader = GitHubSarifUploader()
        error = uploader._check_gh_authenticated()
        assert error is None

    @patch.object(GitHubSarifUploader, "_check_gh_installed")
    def test_upload_gh_not_installed(self, mock_check: MagicMock) -> None:
        mock_check.return_value = "GitHub CLI (gh) not installed"
        uploader = GitHubSarifUploader()
        result = uploader.upload(Path("/tmp/test.sarif"), Path("/tmp/project"))
        assert result.success is False
        assert result.error_type == "GhNotInstalled"

    @patch.object(GitHubSarifUploader, "_check_gh_installed")
    @patch.object(GitHubSarifUploader, "_check_gh_authenticated")
    def test_upload_gh_not_authenticated(
        self, mock_auth: MagicMock, mock_install: MagicMock
    ) -> None:
        mock_install.return_value = None
        mock_auth.return_value = "Not logged in to GitHub"
        uploader = GitHubSarifUploader()
        result = uploader.upload(Path("/tmp/test.sarif"), Path("/tmp/project"))
        assert result.success is False
        assert result.error_type == "GhNotAuthenticated"

    @patch.object(GitHubSarifUploader, "_check_gh_installed")
    @patch.object(GitHubSarifUploader, "_check_gh_authenticated")
    @patch.object(GitHubSarifUploader, "get_git_info")
    def test_upload_file_not_found(
        self,
        mock_git_info: MagicMock,
        mock_auth: MagicMock,
        mock_install: MagicMock,
    ) -> None:
        mock_install.return_value = None
        mock_auth.return_value = None
        mock_git_info.return_value = {
            "commit_sha": "a" * 40,
            "ref": "refs/heads/main",
            "owner": "owner",
            "repo": "repo",
        }
        uploader = GitHubSarifUploader()
        result = uploader.upload(
            Path("/nonexistent/test.sarif"), Path("/tmp")
        )
        assert result.success is False
        assert result.error_type == "FileNotFound"
