"""
Service layer for secure, time-limited client link token generation and resolution.
"""

import secrets
from datetime import UTC, datetime, timedelta
from typing import Any
from uuid import UUID

from app.config import settings
from app.core.exceptions import NotFoundException, ValidationException
from app.core.state_store import (
    get_client_token_payload,
    revoke_client_token,
    store_client_token,
)


class ClientLinkService:
    """Manages cryptographic client tokens stored in PostgreSQL with 72-hour TTL."""

    def __init__(self, ttl_hours: int | None = None, base_url: str | None = None) -> None:
        self.ttl_seconds = (ttl_hours or settings.CLIENT_TOKEN_TTL_HOURS) * 3600
        self.base_url = (base_url or settings.CLIENT_APP_BASE_URL).rstrip("/")

    def generate_token(self) -> str:
        """Generate a cryptographically secure 43-character URL-safe token."""
        return secrets.token_urlsafe(32)

    def build_report_url(self, token: str) -> str:
        """Construct full public client URL for accessing diagnostic report."""
        return f"{self.base_url}/client/reports/{token}"

    def build_appointment_url(self, token: str) -> str:
        """Construct full public client URL for accessing appointment details."""
        return f"{self.base_url}/client/appointments/{token}"

    async def create_report_token(
        self,
        report_id: UUID,
        patient_id: UUID,
        lab_id: UUID,
    ) -> tuple[str, str, datetime]:
        """
        Generate and persist a 72-hour access token for a released report.
        Returns (token, full_url, expiration_datetime).
        """
        token = self.generate_token()
        now = datetime.now(UTC)
        expires_at = now + timedelta(seconds=self.ttl_seconds)

        payload: dict[str, Any] = {
            "token_type": "report",
            "target_id": str(report_id),
            "patient_id": str(patient_id),
            "lab_id": str(lab_id),
            "created_at": now.isoformat(),
            "expires_at": expires_at.isoformat(),
        }

        await store_client_token(token, payload, expires_in_seconds=self.ttl_seconds)
        url = self.build_report_url(token)
        return token, url, expires_at

    async def create_appointment_token(
        self,
        appointment_id: UUID,
        patient_id: UUID,
        lab_id: UUID,
    ) -> tuple[str, str, datetime]:
        """
        Generate and persist a 72-hour access token for an appointment.
        Returns (token, full_url, expiration_datetime).
        """
        token = self.generate_token()
        now = datetime.now(UTC)
        expires_at = now + timedelta(seconds=self.ttl_seconds)

        payload: dict[str, Any] = {
            "token_type": "appointment",
            "target_id": str(appointment_id),
            "patient_id": str(patient_id),
            "lab_id": str(lab_id),
            "created_at": now.isoformat(),
            "expires_at": expires_at.isoformat(),
        }

        await store_client_token(token, payload, expires_in_seconds=self.ttl_seconds)
        url = self.build_appointment_url(token)
        return token, url, expires_at

    async def verify_token(
        self,
        token: str,
        expected_type: str | None = None,
    ) -> dict[str, Any]:
        """
        Validate token existence in PostgreSQL and verify expected resource type.
        Raises NotFoundException if expired or invalid.
        Raises ValidationException if resource type mismatches.
        """
        payload = await get_client_token_payload(token)
        if not payload:
            raise NotFoundException(
                message="This link has expired or is invalid. Please contact the laboratory for assistance."
            )

        token_type = payload.get("token_type")
        if expected_type and token_type != expected_type:
            raise ValidationException(
                message=(
                    f"Invalid link type for this resource. "
                    f"Expected '{expected_type}' but link is for '{token_type}'."
                )
            )

        return payload

    async def revoke_token(self, token: str) -> None:
        """Revoke a token immediately in PostgreSQL."""
        await revoke_client_token(token)
