"""Durable Core repository for canonical Forms submissions."""
from __future__ import annotations

from datetime import UTC
from uuid import UUID

from sqlalchemy import Select, desc, select
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.forms.models import FormSubmissionRecord
from src.domain.forms.persistence import (
    FormPersistenceContext,
    FormPersistenceError,
    PersistedFormSubmission,
    _accepted_fingerprint,
)
from src.domain.forms.runtime import AcceptedFormSubmission


def _project_uuid(value: str) -> UUID:
    try:
        return UUID(value)
    except ValueError as exc:
        raise FormPersistenceError("submission_persistence_failed") from exc


def _to_envelope(row: FormSubmissionRecord) -> PersistedFormSubmission:
    accepted = AcceptedFormSubmission(
        submission_id=row.submission_id,
        submission={"form_key": row.form_key, "values": row.values_json},
        accepted_at=row.accepted_at,
    )
    persisted_at = row.persisted_at
    if persisted_at.tzinfo is None:
        persisted_at = persisted_at.replace(tzinfo=UTC)
    return PersistedFormSubmission(
        project_id=str(row.project_id),
        accepted=accepted,
        persisted_at=persisted_at,
        status=row.status,
    )


class FormSubmissionRepository:
    def __init__(self, db: AsyncSession) -> None:
        self._db = db

    async def persist(
        self,
        context: FormPersistenceContext,
        accepted: AcceptedFormSubmission,
    ) -> PersistedFormSubmission:
        project_id = _project_uuid(context.project_id)
        statement = (
            insert(FormSubmissionRecord)
            .values(
                submission_id=accepted.submission_id,
                project_id=project_id,
                form_key=accepted.submission.form_key,
                values_json=accepted.model_dump(mode="json")["submission"]["values"],
                accepted_at=accepted.accepted_at,
                status="accepted",
            )
            .on_conflict_do_nothing(index_elements=[FormSubmissionRecord.submission_id])
        )
        await self._db.execute(statement)
        row = await self._db.scalar(
            select(FormSubmissionRecord).where(
                FormSubmissionRecord.submission_id == accepted.submission_id
            )
        )
        if row is None or row.project_id != project_id:
            raise FormPersistenceError("submission_identity_conflict")
        existing = _to_envelope(row)
        if _accepted_fingerprint(existing.accepted) != _accepted_fingerprint(accepted):
            raise FormPersistenceError("submission_identity_conflict")
        return existing

    async def get(
        self, context: FormPersistenceContext, submission_id: str
    ) -> PersistedFormSubmission | None:
        row = await self._db.scalar(
            select(FormSubmissionRecord).where(
                FormSubmissionRecord.project_id == _project_uuid(context.project_id),
                FormSubmissionRecord.submission_id == submission_id,
            )
        )
        return _to_envelope(row) if row else None

    async def list(
        self,
        project_id: UUID,
        *,
        form_key: str | None = None,
        status: str | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> tuple[list[PersistedFormSubmission], int]:
        from sqlalchemy import func

        filters = [FormSubmissionRecord.project_id == project_id]
        if form_key:
            filters.append(FormSubmissionRecord.form_key == form_key)
        if status:
            filters.append(FormSubmissionRecord.status == status)
        count = int(await self._db.scalar(select(func.count()).select_from(FormSubmissionRecord).where(*filters)) or 0)
        statement: Select[tuple[FormSubmissionRecord]] = (
            select(FormSubmissionRecord)
            .where(*filters)
            .order_by(desc(FormSubmissionRecord.persisted_at), desc(FormSubmissionRecord.submission_id))
            .offset(offset)
            .limit(limit)
        )
        rows = list((await self._db.scalars(statement)).all())
        return [_to_envelope(row) for row in rows], count
