"""Data access for workspace user GitHub installations + project mirror targets."""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Optional
from uuid import UUID

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.github_user_mirror.models import (
    ProjectUserMirrorTarget,
    WorkspaceUserGithubInstallation,
)


class WorkspaceUserGithubInstallationRepository:
    def __init__(self, db: AsyncSession):
        self._db = db

    async def flush(self) -> None:
        await self._db.flush()

    async def get_by_id(self, row_id: UUID) -> Optional[WorkspaceUserGithubInstallation]:
        r = await self._db.execute(
            select(WorkspaceUserGithubInstallation).where(
                WorkspaceUserGithubInstallation.id == row_id
            )
        )
        return r.scalar_one_or_none()

    async def get_by_github_installation_id(
        self, installation_id: int
    ) -> Optional[WorkspaceUserGithubInstallation]:
        r = await self._db.execute(
            select(WorkspaceUserGithubInstallation).where(
                WorkspaceUserGithubInstallation.github_installation_id == installation_id
            )
            .order_by(WorkspaceUserGithubInstallation.created_at.desc())
            .limit(1)
        )
        return r.scalar_one_or_none()

    async def get_by_workspace_and_github_installation_id(
        self, workspace_id: UUID, installation_id: int
    ) -> Optional[WorkspaceUserGithubInstallation]:
        r = await self._db.execute(
            select(WorkspaceUserGithubInstallation).where(
                WorkspaceUserGithubInstallation.workspace_id == workspace_id,
                WorkspaceUserGithubInstallation.github_installation_id == installation_id,
            )
        )
        return r.scalar_one_or_none()

    async def list_by_github_installation_id(
        self, installation_id: int
    ) -> list[WorkspaceUserGithubInstallation]:
        r = await self._db.execute(
            select(WorkspaceUserGithubInstallation).where(
                WorkspaceUserGithubInstallation.github_installation_id == installation_id
            )
        )
        return list(r.scalars().all())

    async def list_for_workspace(self, workspace_id: UUID) -> list[WorkspaceUserGithubInstallation]:
        r = await self._db.execute(
            select(WorkspaceUserGithubInstallation)
            .where(WorkspaceUserGithubInstallation.workspace_id == workspace_id)
            .order_by(WorkspaceUserGithubInstallation.created_at.desc())
        )
        return list(r.scalars().all())

    async def create(
        self,
        *,
        workspace_id: UUID,
        github_installation_id: int,
        installation_id_ciphertext: Optional[str],
        account_login: str,
        account_id: Optional[int],
        account_type: str,
        repository_selection: str,
        created_by: Optional[UUID],
    ) -> WorkspaceUserGithubInstallation:
        row = WorkspaceUserGithubInstallation(
            workspace_id=workspace_id,
            github_installation_id=github_installation_id,
            installation_id_ciphertext=installation_id_ciphertext,
            account_login=account_login,
            account_id=account_id,
            account_type=account_type,
            repository_selection=repository_selection,
            created_by=created_by,
        )
        self._db.add(row)
        await self._db.flush()
        await self._db.refresh(row)
        return row

    async def delete(self, row_id: UUID) -> bool:
        row = await self.get_by_id(row_id)
        if not row:
            return False
        await self._db.delete(row)
        await self._db.flush()
        return True

    async def set_suspended(self, github_installation_id: int, suspended: bool) -> None:
        rows = await self.list_by_github_installation_id(github_installation_id)
        if not rows:
            return
        suspended_at = datetime.now(timezone.utc) if suspended else None
        for row in rows:
            row.suspended_at = suspended_at
        await self.flush()


class ProjectUserMirrorRepository:
    def __init__(self, db: AsyncSession):
        self._db = db

    async def get_by_project_id(self, project_id: UUID) -> Optional[ProjectUserMirrorTarget]:
        r = await self._db.execute(
            select(ProjectUserMirrorTarget).where(ProjectUserMirrorTarget.project_id == project_id)
        )
        return r.scalar_one_or_none()

    async def upsert(
        self,
        *,
        project_id: UUID,
        workspace_user_github_installation_id: UUID,
        target_repo_full_name: str,
        target_default_branch: str,
        enabled: bool,
    ) -> ProjectUserMirrorTarget:
        existing = await self.get_by_project_id(project_id)
        if existing:
            existing.workspace_user_github_installation_id = workspace_user_github_installation_id
            existing.target_repo_full_name = target_repo_full_name
            existing.target_default_branch = target_default_branch
            existing.enabled = enabled
            await self._db.flush()
            await self._db.refresh(existing)
            return existing
        row = ProjectUserMirrorTarget(
            project_id=project_id,
            workspace_user_github_installation_id=workspace_user_github_installation_id,
            target_repo_full_name=target_repo_full_name,
            target_default_branch=target_default_branch,
            enabled=enabled,
        )
        self._db.add(row)
        await self._db.flush()
        await self._db.refresh(row)
        return row

    async def update_push_result(
        self,
        project_id: UUID,
        *,
        last_push_sha: Optional[str] = None,
        last_error: Optional[str] = None,
    ) -> Optional[ProjectUserMirrorTarget]:
        row = await self.get_by_project_id(project_id)
        if not row:
            return None
        if last_push_sha is not None:
            row.last_push_sha = last_push_sha
        row.last_error = last_error
        await self._db.flush()
        return row
