"""Service for editing Forgejo issues and updating local cache."""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

from sqlmodel import select

from app.core.logging import get_logger
from app.core.time import utcnow
from app.db import crud
from app.models.forgejo_connections import ForgejoConnection
from app.models.forgejo_issues import ForgejoIssue
from app.models.forgejo_repositories import ForgejoRepository
from app.services.forgejo_client import get_forgejo_client

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio.session import AsyncSession

logger = get_logger(__name__)


class EditIssueError(Exception):
    """Base exception for edit issue errors."""


class EditIssueNotFoundError(EditIssueError):
    """Raised when issue, repository, or connection is not found."""


class EditIssueRemoteError(EditIssueError):
    """Raised when the Forgejo API call fails."""


async def edit_cached_issue(
    session: AsyncSession,
    issue: ForgejoIssue,
    *,
    title: str | None = None,
    body: str | None = None,
    state: str | None = None,
    actor_user_id: UUID | None = None,
    actor_agent_id: UUID | None = None,
) -> dict[str, object]:
    """Edit a Forgejo issue remotely and sync changes to the local cache.

    Raises:
        EditIssueNotFoundError: If repository or connection not found.
        EditIssueRemoteError: If the Forgejo API call fails.
    """
    repository = await crud.get_by_id(session, ForgejoRepository, issue.repository_id)
    if repository is None:
        raise EditIssueNotFoundError("Repository not found")

    connection = await crud.get_by_id(session, ForgejoConnection, repository.connection_id)
    if connection is None:
        raise EditIssueNotFoundError("Connection not found")

    try:
        async with get_forgejo_client(connection) as client:
            updated = await client.edit_issue(
                owner=repository.owner,
                repo=repository.repo,
                issue_number=issue.forgejo_issue_number,
                title=title,
                body=body,
                state=state,
            )
    except Exception as exc:
        raise EditIssueRemoteError(f"Failed to edit issue on Forgejo: {exc}") from exc

    now = utcnow()

    if title is not None:
        issue.title = title
    if body is not None:
        issue.body = body
        issue.body_preview = body[:1000] if body else None
    if state is not None:
        issue.state = state
        if state == "closed" and issue.forgejo_closed_at is None:
            issue.forgejo_closed_at = now

    issue.forgejo_updated_at = now
    issue.last_synced_at = now

    if isinstance(issue.forgejo_payload, dict):
        payload = dict(issue.forgejo_payload)
        if title is not None:
            payload["title"] = title
        if body is not None:
            payload["body"] = body
        if state is not None:
            payload["state"] = state
        payload["updated_at"] = now.isoformat()
        issue.forgejo_payload = payload

    session.add(issue)
    await session.flush()

    logger.info(
        "forgejo.issue.edited",
        extra={
            "issue_id": str(issue.id),
            "forgejo_issue_number": issue.forgejo_issue_number,
            "repository_id": str(repository.id),
            "fields_changed": [
                f for f, v in [("title", title), ("body", body), ("state", state)] if v is not None
            ],
            "actor_user_id": str(actor_user_id) if actor_user_id else None,
            "actor_agent_id": str(actor_agent_id) if actor_agent_id else None,
        },
    )

    _ = updated
    return {
        "success": True,
        "issue_id": str(issue.id),
        "forgejo_issue_number": issue.forgejo_issue_number,
        "title": issue.title,
        "body": issue.body,
        "state": issue.state,
        "forgejo_updated_at": issue.forgejo_updated_at.isoformat(),
    }


async def edit_issue_by_id(
    session: AsyncSession,
    issue_id: UUID,
    *,
    title: str | None = None,
    body: str | None = None,
    state: str | None = None,
    actor_user_id: UUID | None = None,
    actor_agent_id: UUID | None = None,
) -> dict[str, object]:
    """Edit a Forgejo issue by its local ID."""
    issue = (await session.exec(select(ForgejoIssue).where(ForgejoIssue.id == issue_id))).first()
    if issue is None:
        raise EditIssueNotFoundError("Issue not found")
    return await edit_cached_issue(
        session=session,
        issue=issue,
        title=title,
        body=body,
        state=state,
        actor_user_id=actor_user_id,
        actor_agent_id=actor_agent_id,
    )
