"""Data access for user GitLab OAuth connections and project mirrors."""
from __future__ import annotations

from datetime import datetime
from typing import Optional
from uuid import UUID

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

from src.domain.gitlab_user_mirror.models import (
    ProjectUserGitlabMirrorTarget,
    WorkspaceUserGitlabConnection,
)


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

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

    async def get_by_workspace_and_user(
        self,
        workspace_id: UUID,
        gitlab_user_id: int,
        base_url: str,
    ) -> Optional[WorkspaceUserGitlabConnection]:
        r = await self._db.execute(
            select(WorkspaceUserGitlabConnection).where(
                WorkspaceUserGitlabConnection.workspace_id == workspace_id,
                WorkspaceUserGitlabConnection.gitlab_user_id == gitlab_user_id,
                WorkspaceUserGitlabConnection.base_url == base_url,
            )
        )
        return r.scalar_one_or_none()

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

    async def upsert(
        self,
        *,
        workspace_id: UUID,
        gitlab_user_id: int,
        username: str,
        name: Optional[str],
        base_url: str,
        access_token_ciphertext: str,
        refresh_token_ciphertext: Optional[str],
        token_expires_at: Optional[datetime],
        created_by: Optional[UUID],
    ) -> WorkspaceUserGitlabConnection:
        existing = await self.get_by_workspace_and_user(workspace_id, gitlab_user_id, base_url)
        if existing:
            existing.username = username
            existing.name = name
            existing.access_token_ciphertext = access_token_ciphertext
            existing.refresh_token_ciphertext = refresh_token_ciphertext
            existing.token_expires_at = token_expires_at
            existing.revoked_at = None
            await self._db.flush()
            await self._db.refresh(existing)
            return existing
        row = WorkspaceUserGitlabConnection(
            workspace_id=workspace_id,
            gitlab_user_id=gitlab_user_id,
            username=username,
            name=name,
            base_url=base_url,
            access_token_ciphertext=access_token_ciphertext,
            refresh_token_ciphertext=refresh_token_ciphertext,
            token_expires_at=token_expires_at,
            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


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

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

    async def list_by_target_path(
        self,
        target_project_full_path: str,
    ) -> list[ProjectUserGitlabMirrorTarget]:
        r = await self._db.execute(
            select(ProjectUserGitlabMirrorTarget).where(
                ProjectUserGitlabMirrorTarget.target_project_full_path == target_project_full_path
            )
        )
        return list(r.scalars().all())

    async def upsert(
        self,
        *,
        project_id: UUID,
        workspace_user_gitlab_connection_id: UUID,
        target_project_full_path: str,
        target_default_branch: str,
        enabled: bool,
    ) -> ProjectUserGitlabMirrorTarget:
        existing = await self.get_by_project_id(project_id)
        if existing:
            existing.workspace_user_gitlab_connection_id = workspace_user_gitlab_connection_id
            existing.target_project_full_path = target_project_full_path
            existing.target_default_branch = target_default_branch
            existing.enabled = enabled
            await self._db.flush()
            await self._db.refresh(existing)
            return existing
        row = ProjectUserGitlabMirrorTarget(
            project_id=project_id,
            workspace_user_gitlab_connection_id=workspace_user_gitlab_connection_id,
            target_project_full_path=target_project_full_path,
            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[ProjectUserGitlabMirrorTarget]:
        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

    async def disable_by_target_path(self, target_project_full_path: str, reason: str) -> int:
        rows = await self.list_by_target_path(target_project_full_path)
        for row in rows:
            row.enabled = False
            row.last_error = reason
        await self._db.flush()
        return len(rows)

    async def rename_target_path(self, old_path: str, new_path: str) -> int:
        rows = await self.list_by_target_path(old_path)
        for row in rows:
            row.target_project_full_path = new_path
            row.last_error = None
        await self._db.flush()
        return len(rows)

    async def update_webhook_id(
        self, project_id: UUID, webhook_id: Optional[str]
    ) -> Optional[ProjectUserGitlabMirrorTarget]:
        row = await self.get_by_project_id(project_id)
        if not row:
            return None
        row.target_project_webhook_id = webhook_id
        await self._db.flush()
        return row

    async def list_by_connection_id(
        self, connection_id: UUID
    ) -> list[ProjectUserGitlabMirrorTarget]:
        r = await self._db.execute(
            select(ProjectUserGitlabMirrorTarget).where(
                ProjectUserGitlabMirrorTarget.workspace_user_gitlab_connection_id == connection_id
            )
        )
        return list(r.scalars().all())

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