# #region Test.GitSchemas [C:2] [TYPE Module] [SEMANTICS test,git,schemas,pydantic]
# @BRIEF Pydantic model validation tests for all git_schemas.py models.
# @RELATION BINDS_TO -> [GitSchemas]
# @TEST_CONTRACT: GitServerConfigBase -> validates all required fields
# @TEST_CONTRACT: GitServerConfigCreate -> extends base with optional config_id
# @TEST_CONTRACT: GitServerConfigUpdate -> all fields optional for partial update
# @TEST_CONTRACT: GitServerConfigSchema -> includes id, status, last_validated
# @TEST_CONTRACT: GitRepositorySchema -> tracks repository binding metadata
# @TEST_CONTRACT: BranchSchema -> validates name, commit_hash, is_remote
# @TEST_CONTRACT: CommitSchema -> validates full commit metadata
# @TEST_CONTRACT: BranchCreate -> name + from_branch required
# @TEST_CONTRACT: ConflictResolution -> validates resolution pattern ^(mine|theirs|manual)$
# @TEST_CONTRACT: MergeStatusSchema -> validates merge state defaults
# @TEST_CONTRACT: MergeConflictFileSchema -> file_path required, mine/theirs optional
# @TEST_CONTRACT: MergeResolveRequest -> wraps ConflictResolution list
# @TEST_CONTRACT: MergeContinueRequest -> optional message
# @TEST_CONTRACT: DeployRequest -> environment_id required
# @TEST_CONTRACT: RepoInitRequest -> config_id + remote_url required
# @TEST_CONTRACT: RepositoryBindingSchema -> all fields required
# @TEST_CONTRACT: RepoStatusBatchRequest -> optional dashboard_ids list
# @TEST_CONTRACT: RepoStatusBatchResponse -> statuses dict[str, dict]
# @TEST_CONTRACT: PromoteRequest -> validates mode pattern ^(mr|direct)$
# @TEST_CONTRACT: PromoteResponse -> all fields with defaults
# @TEST_CONTRACT: GiteaRepoSchema, RemoteRepoSchema -> remote repo descriptors
# @TEST_CONTRACT: GiteaRepoCreateRequest, RemoteRepoCreateRequest -> create payloads
# @TEST_EDGE: missing_required_field -> pydantic.ValidationError
# @TEST_EDGE: invalid_resolution_pattern -> pattern mismatch
# @TEST_EDGE: invalid_mode_pattern -> pattern mismatch
# @TEST_EDGE: empty_name -> max_length=255 but min_length=1 enforced

from datetime import datetime, timezone
from enum import Enum

import pytest
from pydantic import ValidationError


class TestGitServerConfigBase:
    """GitServerConfigBase — base configuration schema."""

    def test_minimal_valid(self):
        from src.api.routes.git_schemas import GitServerConfigBase
        from src.models.git import GitProvider

        obj = GitServerConfigBase(
            name="My Git Server",
            provider=GitProvider.GITHUB,
            url="https://github.com",
            pat="ghp_test123",
        )
        assert obj.name == "My Git Server"
        assert obj.provider == GitProvider.GITHUB
        assert obj.url == "https://github.com"
        assert obj.pat == "ghp_test123"
        assert obj.default_repository is None
        assert obj.default_branch == "main"

    def test_missing_required_fields(self):
        from src.api.routes.git_schemas import GitServerConfigBase

        with pytest.raises(ValidationError):
            GitServerConfigBase()

    def test_missing_pat(self):
        from src.api.routes.git_schemas import GitServerConfigBase
        from src.models.git import GitProvider

        with pytest.raises(ValidationError):
            GitServerConfigBase(
                name="Test",
                provider=GitProvider.GITHUB,
                url="https://github.com",
            )

    def test_with_defaults(self):
        from src.api.routes.git_schemas import GitServerConfigBase
        from src.models.git import GitProvider

        obj = GitServerConfigBase(
            name="Test",
            provider=GitProvider.GITLAB,
            url="https://gitlab.com",
            pat="glpat-abc",
            default_repository="org/repo",
            default_branch="develop",
        )
        assert obj.default_repository == "org/repo"
        assert obj.default_branch == "develop"

    def test_duplicate_pat_field(self):
        """The source has `pat: str` declared twice (lines 23-24). Verify it works."""
        from src.api.routes.git_schemas import GitServerConfigBase
        from src.models.git import GitProvider

        obj = GitServerConfigBase(
            name="Dup", provider=GitProvider.GITEA, url="https://gitea.dev", pat="token123"
        )
        assert obj.pat == "token123"


class TestGitServerConfigCreate:
    """GitServerConfigCreate — creation schema extends base."""

    def test_valid(self):
        from src.api.routes.git_schemas import GitServerConfigCreate
        from src.models.git import GitProvider

        obj = GitServerConfigCreate(
            name="New Server",
            provider=GitProvider.GITHUB,
            url="https://github.com",
            pat="ghp_new",
            config_id="cfg-123",
        )
        assert obj.config_id == "cfg-123"

    def test_without_config_id(self):
        from src.api.routes.git_schemas import GitServerConfigCreate
        from src.models.git import GitProvider

        obj = GitServerConfigCreate(
            name="New Server",
            provider=GitProvider.GITHUB,
            url="https://github.com",
            pat="ghp_new",
        )
        assert obj.config_id is None


class TestGitServerConfigUpdate:
    """GitServerConfigUpdate — all fields optional for partial update."""

    def test_empty_update(self):
        from src.api.routes.git_schemas import GitServerConfigUpdate

        obj = GitServerConfigUpdate()
        assert obj.name is None
        assert obj.provider is None
        assert obj.url is None
        assert obj.pat is None
        assert obj.default_repository is None
        assert obj.default_branch is None

    def test_partial_update(self):
        from src.api.routes.git_schemas import GitServerConfigUpdate
        from src.models.git import GitProvider

        obj = GitServerConfigUpdate(name="Updated Name", provider=GitProvider.GITLAB)
        assert obj.name == "Updated Name"
        assert obj.provider == GitProvider.GITLAB
        assert obj.url is None


class TestGitServerConfigSchema:
    """GitServerConfigSchema — includes id, status, last_validated."""

    def test_valid(self):
        from src.api.routes.git_schemas import GitServerConfigSchema
        from src.models.git import GitProvider, GitStatus

        now = datetime.now(timezone.utc)
        obj = GitServerConfigSchema(
            id="cfg-1",
            name="Server",
            provider=GitProvider.GITHUB,
            url="https://github.com",
            pat="ghp_xxx",
            status=GitStatus.CONNECTED,
            last_validated=now,
        )
        assert obj.id == "cfg-1"
        assert obj.status == GitStatus.CONNECTED
        assert obj.last_validated == now
        assert obj.model_config.get("from_attributes") is True


class TestGitRepositorySchema:
    """GitRepositorySchema — repository binding."""

    def test_valid(self):
        from src.api.routes.git_schemas import GitRepositorySchema
        from src.models.git import SyncStatus

        obj = GitRepositorySchema(
            id="repo-1",
            dashboard_id=42,
            config_id="cfg-1",
            remote_url="https://github.com/org/repo.git",
            local_path="/data/repos/dash-42",
            current_branch="main",
            sync_status=SyncStatus.CLEAN,
        )
        assert obj.dashboard_id == 42
        assert obj.sync_status == SyncStatus.CLEAN
        assert obj.model_config.get("from_attributes") is True


class TestBranchSchema:
    """BranchSchema — branch metadata."""

    def test_valid(self):
        from src.api.routes.git_schemas import BranchSchema

        now = datetime.now(timezone.utc)
        obj = BranchSchema(
            name="feature/test",
            commit_hash="abc123def456",
            is_remote=False,
            last_updated=now,
        )
        assert obj.name == "feature/test"
        assert obj.commit_hash == "abc123def456"
        assert obj.is_remote is False


class TestCommitSchema:
    """CommitSchema — commit details."""

    def test_valid(self):
        from src.api.routes.git_schemas import CommitSchema

        now = datetime.now(timezone.utc)
        obj = CommitSchema(
            hash="a1b2c3d4",
            author="Test User",
            email="test@example.com",
            timestamp=now,
            message="Fix dashboard layout",
            files_changed=["dashboard.json", "chart.yaml"],
        )
        assert obj.hash == "a1b2c3d4"
        assert len(obj.files_changed) == 2

    def test_empty_files(self):
        from src.api.routes.git_schemas import CommitSchema

        now = datetime.now(timezone.utc)
        obj = CommitSchema(
            hash="abc", author="A", email="a@b.com",
            timestamp=now, message="x", files_changed=[],
        )
        assert obj.files_changed == []


class TestBranchCreate:
    """BranchCreate — branch creation request."""

    def test_valid(self):
        from src.api.routes.git_schemas import BranchCreate

        obj = BranchCreate(name="feature/new", from_branch="main")
        assert obj.name == "feature/new"
        assert obj.from_branch == "main"

    def test_missing_name(self):
        from src.api.routes.git_schemas import BranchCreate

        with pytest.raises(ValidationError):
            BranchCreate(from_branch="main")  # name missing


class TestBranchCheckout:
    """BranchCheckout — checkout request."""

    def test_valid(self):
        from src.api.routes.git_schemas import BranchCheckout

        obj = BranchCheckout(name="feature/test")
        assert obj.name == "feature/test"


class TestCommitCreate:
    """CommitCreate — staging and committing."""

    def test_valid(self):
        from src.api.routes.git_schemas import CommitCreate

        obj = CommitCreate(message="Updated dashboard", files=["dash.json"])
        assert obj.message == "Updated dashboard"
        assert obj.files == ["dash.json"]


class TestConflictResolution:
    """ConflictResolution — pattern validation for resolution field."""

    def test_valid_resolutions(self):
        from src.api.routes.git_schemas import ConflictResolution

        for res in ("mine", "theirs", "manual"):
            obj = ConflictResolution(file_path="dash.json", resolution=res)
            assert obj.resolution == res

    def test_invalid_resolution(self):
        from src.api.routes.git_schemas import ConflictResolution

        with pytest.raises(ValidationError):
            ConflictResolution(file_path="dash.json", resolution="auto")

    def test_empty_resolution(self):
        from src.api.routes.git_schemas import ConflictResolution

        with pytest.raises(ValidationError):
            ConflictResolution(file_path="dash.json", resolution="")

    def test_with_content(self):
        from src.api.routes.git_schemas import ConflictResolution

        obj = ConflictResolution(
            file_path="dash.json", resolution="manual", content='{"fixed": true}'
        )
        assert obj.content == '{"fixed": true}'


class TestMergeStatusSchema:
    """MergeStatusSchema — merge state."""

    def test_minimal(self):
        from src.api.routes.git_schemas import MergeStatusSchema

        obj = MergeStatusSchema(
            has_unfinished_merge=False,
            repository_path="/data/repos/dash-42",
            git_dir="/data/repos/dash-42/.git",
            current_branch="main",
        )
        assert obj.has_unfinished_merge is False
        assert obj.conflicts_count == 0
        assert obj.merge_head is None

    def test_with_conflicts(self):
        from src.api.routes.git_schemas import MergeStatusSchema

        obj = MergeStatusSchema(
            has_unfinished_merge=True,
            repository_path="/data/repos/dash-42",
            git_dir="/data/repos/dash-42/.git",
            current_branch="feature/x",
            merge_head="abc123",
            merge_message_preview="Merge branch 'feature/y'",
            conflicts_count=3,
        )
        assert obj.has_unfinished_merge is True
        assert obj.merge_head == "abc123"
        assert obj.conflicts_count == 3


class TestMergeConflictFileSchema:
    """MergeConflictFileSchema — conflicted file with optional snapshots."""

    def test_minimal(self):
        from src.api.routes.git_schemas import MergeConflictFileSchema

        obj = MergeConflictFileSchema(file_path="dash.json")
        assert obj.file_path == "dash.json"
        assert obj.mine is None
        assert obj.theirs is None

    def test_with_snapshots(self):
        from src.api.routes.git_schemas import MergeConflictFileSchema

        obj = MergeConflictFileSchema(
            file_path="chart.yaml", mine='{"v": 1}', theirs='{"v": 2}'
        )
        assert obj.mine == '{"v": 1}'
        assert obj.theirs == '{"v": 2}'


class TestMergeResolveRequest:
    """MergeResolveRequest — wraps conflict resolutions."""

    def test_empty(self):
        from src.api.routes.git_schemas import MergeResolveRequest

        obj = MergeResolveRequest()
        assert obj.resolutions == []

    def test_with_resolutions(self):
        from src.api.routes.git_schemas import MergeResolveRequest, ConflictResolution

        obj = MergeResolveRequest(
            resolutions=[
                ConflictResolution(file_path="a.json", resolution="mine"),
                ConflictResolution(file_path="b.json", resolution="theirs"),
            ]
        )
        assert len(obj.resolutions) == 2


class TestMergeContinueRequest:
    """MergeContinueRequest — optional commit message."""

    def test_without_message(self):
        from src.api.routes.git_schemas import MergeContinueRequest

        obj = MergeContinueRequest()
        assert obj.message is None

    def test_with_message(self):
        from src.api.routes.git_schemas import MergeContinueRequest

        obj = MergeContinueRequest(message="Finish merge")
        assert obj.message == "Finish merge"


class TestDeploymentEnvironmentSchema:
    """DeploymentEnvironmentSchema — deployment target."""

    def test_valid(self):
        from src.api.routes.git_schemas import DeploymentEnvironmentSchema

        obj = DeploymentEnvironmentSchema(
            id="env-prod",
            name="Production",
            superset_url="https://superset.example.com",
            is_active=True,
        )
        assert obj.id == "env-prod"
        assert obj.is_active is True
        assert obj.model_config.get("from_attributes") is True


class TestDeployRequest:
    """DeployRequest — deployment request."""

    def test_valid(self):
        from src.api.routes.git_schemas import DeployRequest

        obj = DeployRequest(environment_id="env-prod")
        assert obj.environment_id == "env-prod"

    def test_missing_environment_id(self):
        from src.api.routes.git_schemas import DeployRequest

        with pytest.raises(ValidationError):
            DeployRequest()


class TestRepoInitRequest:
    """RepoInitRequest — repository initialization."""

    def test_valid(self):
        from src.api.routes.git_schemas import RepoInitRequest

        obj = RepoInitRequest(
            config_id="cfg-1", remote_url="https://github.com/org/repo.git"
        )
        assert obj.config_id == "cfg-1"
        assert obj.remote_url == "https://github.com/org/repo.git"


class TestRepositoryBindingSchema:
    """RepositoryBindingSchema — binding descriptor."""

    def test_valid(self):
        from src.api.routes.git_schemas import RepositoryBindingSchema
        from src.models.git import GitProvider

        obj = RepositoryBindingSchema(
            dashboard_id=42,
            config_id="cfg-1",
            provider=GitProvider.GITHUB,
            remote_url="https://github.com/org/repo.git",
            local_path="/data/repos/dash-42",
        )
        assert obj.dashboard_id == 42
        assert obj.provider == GitProvider.GITHUB


class TestRepoStatusBatchRequest:
    """RepoStatusBatchRequest — batch status request."""

    def test_default_empty(self):
        from src.api.routes.git_schemas import RepoStatusBatchRequest

        obj = RepoStatusBatchRequest()
        assert obj.dashboard_ids == []

    def test_with_ids(self):
        from src.api.routes.git_schemas import RepoStatusBatchRequest

        obj = RepoStatusBatchRequest(dashboard_ids=[1, 2, 3])
        assert obj.dashboard_ids == [1, 2, 3]


class TestRepoStatusBatchResponse:
    """RepoStatusBatchResponse — batch status response."""

    def test_valid(self):
        from src.api.routes.git_schemas import RepoStatusBatchResponse

        obj = RepoStatusBatchResponse(
            statuses={"42": {"branch": "main", "sync": "synced"}}
        )
        assert obj.statuses["42"]["branch"] == "main"


class TestPromoteRequest:
    """PromoteRequest — branch promotion with pattern validation."""

    def test_valid_mr_mode(self):
        from src.api.routes.git_schemas import PromoteRequest

        obj = PromoteRequest(from_branch="feature/x", to_branch="main", mode="mr")
        assert obj.mode == "mr"
        assert obj.draft is False
        assert obj.remove_source_branch is False

    def test_valid_direct_mode(self):
        from src.api.routes.git_schemas import PromoteRequest

        obj = PromoteRequest(from_branch="hotfix", to_branch="prod", mode="direct")
        assert obj.mode == "direct"

    def test_invalid_mode(self):
        from src.api.routes.git_schemas import PromoteRequest

        with pytest.raises(ValidationError):
            PromoteRequest(from_branch="a", to_branch="b", mode="merge")

    def test_with_all_options(self):
        from src.api.routes.git_schemas import PromoteRequest

        obj = PromoteRequest(
            from_branch="feature/new",
            to_branch="staging",
            mode="mr",
            title="Promote new feature",
            description="Full description",
            reason="Business need",
            draft=True,
            remove_source_branch=True,
        )
        assert obj.title == "Promote new feature"
        assert obj.draft is True
        assert obj.remove_source_branch is True

    def test_empty_from_branch(self):
        from src.api.routes.git_schemas import PromoteRequest

        with pytest.raises(ValidationError):
            PromoteRequest(from_branch="", to_branch="main", mode="mr")


class TestPromoteResponse:
    """PromoteResponse — promotion result."""

    def test_minimal(self):
        from src.api.routes.git_schemas import PromoteResponse

        obj = PromoteResponse(
            mode="mr",
            from_branch="feature/x",
            to_branch="main",
            status="merged",
        )
        assert obj.url is None
        assert obj.reference_id is None
        assert obj.policy_violation is False

    def test_full(self):
        from src.api.routes.git_schemas import PromoteResponse

        obj = PromoteResponse(
            mode="direct",
            from_branch="hotfix",
            to_branch="prod",
            status="deployed",
            url="https://git.example.com/merge/42",
            reference_id="42",
            policy_violation=True,
        )
        assert obj.url == "https://git.example.com/merge/42"
        assert obj.policy_violation is True


class TestGiteaRepoSchema:
    """GiteaRepoSchema — Gitea repository descriptor."""

    def test_minimal(self):
        from src.api.routes.git_schemas import GiteaRepoSchema

        obj = GiteaRepoSchema(name="my-repo", full_name="org/my-repo")
        assert obj.private is False
        assert obj.clone_url is None

    def test_full(self):
        from src.api.routes.git_schemas import GiteaRepoSchema

        obj = GiteaRepoSchema(
            name="my-repo",
            full_name="org/my-repo",
            private=True,
            clone_url="https://gitea.dev/org/my-repo.git",
            html_url="https://gitea.dev/org/my-repo",
            ssh_url="git@gitea.dev:org/my-repo.git",
            default_branch="main",
        )
        assert obj.private is True
        assert obj.ssh_url == "git@gitea.dev:org/my-repo.git"


class TestGiteaRepoCreateRequest:
    """GiteaRepoCreateRequest — create payload with validation."""

    def test_minimal(self):
        from src.api.routes.git_schemas import GiteaRepoCreateRequest

        obj = GiteaRepoCreateRequest(name="new-repo")
        assert obj.private is True
        assert obj.auto_init is True
        assert obj.default_branch == "main"

    def test_empty_name(self):
        from src.api.routes.git_schemas import GiteaRepoCreateRequest

        with pytest.raises(ValidationError):
            GiteaRepoCreateRequest(name="")

    def test_public_repo(self):
        from src.api.routes.git_schemas import GiteaRepoCreateRequest

        obj = GiteaRepoCreateRequest(name="public-repo", private=False)
        assert obj.private is False


class TestRemoteRepoSchema:
    """RemoteRepoSchema — provider-agnostic remote repo."""

    def test_minimal(self):
        from src.api.routes.git_schemas import RemoteRepoSchema
        from src.models.git import GitProvider

        obj = RemoteRepoSchema(
            provider=GitProvider.GITHUB, name="repo", full_name="org/repo"
        )
        assert obj.provider == GitProvider.GITHUB

    def test_full(self):
        from src.api.routes.git_schemas import RemoteRepoSchema
        from src.models.git import GitProvider

        obj = RemoteRepoSchema(
            provider=GitProvider.GITLAB,
            name="repo",
            full_name="org/repo",
            private=True,
            clone_url="https://gitlab.com/org/repo.git",
            html_url="https://gitlab.com/org/repo",
            ssh_url="git@gitlab.com:org/repo.git",
            default_branch="main",
        )
        assert obj.clone_url == "https://gitlab.com/org/repo.git"


class TestRemoteRepoCreateRequest:
    """RemoteRepoCreateRequest — provider-agnostic create."""

    def test_minimal(self):
        from src.api.routes.git_schemas import RemoteRepoCreateRequest

        obj = RemoteRepoCreateRequest(name="new-repo")
        assert obj.private is True
        assert obj.default_branch == "main"

    def test_empty_name(self):
        from src.api.routes.git_schemas import RemoteRepoCreateRequest

        with pytest.raises(ValidationError):
            RemoteRepoCreateRequest(name="")

    def test_custom_branch(self):
        from src.api.routes.git_schemas import RemoteRepoCreateRequest

        obj = RemoteRepoCreateRequest(name="repo", default_branch="develop")
        assert obj.default_branch == "develop"


# #endregion Test.GitSchemas
