"""Billing notification persistence - dedupe and audit."""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Optional, Tuple
from uuid import UUID

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

from src.domain.billing.models import BillingNotification


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

    async def get_existing(self, dedupe_key: str, recipient_email: str) -> Optional[BillingNotification]:
        result = await self._db.execute(
            select(BillingNotification)
            .where(BillingNotification.dedupe_key == dedupe_key)
            .where(BillingNotification.recipient_email == recipient_email)
        )
        return result.scalar_one_or_none()

    async def insert_queued_or_get_duplicate(
        self,
        workspace_id: UUID,
        notification_type: str,
        source_object_type: str,
        source_object_id: str,
        dedupe_key: str,
        recipient_email: str,
    ) -> Tuple[Optional[BillingNotification], bool]:
        """
        Try insert. Returns (notification, is_new).
        If (dedupe_key, recipient) exists, returns (None, False) - skip send.
        On race (IntegrityError), returns (None, False) using savepoint.
        """
        existing = await self.get_existing(dedupe_key, recipient_email)
        if existing:
            return None, False
        rec = BillingNotification(
            workspace_id=workspace_id,
            notification_type=notification_type,
            source_object_type=source_object_type,
            source_object_id=source_object_id,
            dedupe_key=dedupe_key,
            recipient_email=recipient_email,
            status="queued",
        )
        self._db.add(rec)
        try:
            with self._db.begin_nested():
                await self._db.flush()
            await self._db.refresh(rec)
            return rec, True
        except IntegrityError:
            # Savepoint rolled back; expunge to avoid re-insert on commit
            self._db.expunge(rec)
            return None, False

    async def mark_sent(self, notification_id: UUID, provider_message_id: Optional[str] = None) -> None:
        now = datetime.now(timezone.utc)
        result = await self._db.execute(select(BillingNotification).where(BillingNotification.id == notification_id))
        rec = result.scalar_one_or_none()
        if rec:
            rec.status = "sent"
            rec.sent_at = now
            rec.provider_message_id = provider_message_id
            rec.last_error = None
            await self._db.flush()

    async def mark_failed(self, notification_id: UUID, error: str) -> None:
        result = await self._db.execute(select(BillingNotification).where(BillingNotification.id == notification_id))
        rec = result.scalar_one_or_none()
        if rec:
            rec.status = "failed"
            rec.last_error = error[:2000] if error else None
            await self._db.flush()

    async def mark_skipped_duplicate(self, notification_id: UUID) -> None:
        result = await self._db.execute(select(BillingNotification).where(BillingNotification.id == notification_id))
        rec = result.scalar_one_or_none()
        if rec:
            rec.status = "skipped_duplicate"
            await self._db.flush()
