"""Domain value objects for NoteFlow."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from enum import Enum, IntEnum, StrEnum
from typing import NewType
from uuid import UUID

from noteflow.config.constants import (
    OAUTH_FIELD_ACCESS_TOKEN,
    OAUTH_FIELD_REFRESH_TOKEN,
    OAUTH_FIELD_SCOPE,
    OAUTH_FIELD_TOKEN_TYPE,
)

# Type-safe identifiers
MeetingId = NewType("MeetingId", UUID)
AnnotationId = NewType("AnnotationId", UUID)


class AnnotationType(Enum):
    """User annotation type.

    Used to categorize user-created annotations during recording.
    Distinct from LLM-extracted ActionItem/KeyPoint in summaries.
    """

    ACTION_ITEM = "action_item"
    DECISION = "decision"
    NOTE = "note"
    RISK = "risk"


class ExportFormat(Enum):
    """Supported export formats for meetings and transcripts.

    Used by export service and project/workspace settings to
    configure default export behavior.
    """

    MARKDOWN = "markdown"
    HTML = "html"
    PDF = "pdf"


class MeetingState(IntEnum):
    """Meeting lifecycle state.

    State transitions:
        CREATED -> RECORDING -> STOPPING -> STOPPED -> COMPLETED
        Any state -> ERROR (on failure)

    The STOPPING state allows graceful shutdown with audio flush operations.
    """

    UNSPECIFIED = 0
    CREATED = 1
    RECORDING = 2
    STOPPED = 3
    COMPLETED = 4
    ERROR = 5
    STOPPING = 6  # Intermediate state for graceful shutdown

    @classmethod
    def from_int(cls, value: int) -> MeetingState:
        """Convert integer to MeetingState.

        Args:
            value: Integer value.

        Returns:
            Corresponding MeetingState.

        Raises:
            ValueError: If value is not a valid state.
        """
        try:
            return cls(value)
        except ValueError as e:
            raise ValueError(f"Invalid meeting state: {value}") from e

    def can_transition_to(self, target: MeetingState) -> bool:
        """Check if transition to target state is valid.

        Args:
            target: Target state.

        Returns:
            True if transition is valid.
        """
        valid_transitions: dict[MeetingState, set[MeetingState]] = {
            MeetingState.UNSPECIFIED: {MeetingState.CREATED},
            MeetingState.CREATED: {MeetingState.RECORDING, MeetingState.ERROR},
            MeetingState.RECORDING: {MeetingState.STOPPING, MeetingState.ERROR},
            MeetingState.STOPPING: {MeetingState.STOPPED, MeetingState.ERROR},
            MeetingState.STOPPED: {MeetingState.COMPLETED, MeetingState.ERROR},
            MeetingState.COMPLETED: {MeetingState.ERROR},
            MeetingState.ERROR: set(),  # Terminal state
        }
        return target in valid_transitions.get(self, set())


# OAuth value objects for calendar integration


class OAuthProvider(StrEnum):
    """Supported OAuth providers for calendar integration."""

    GOOGLE = "google"
    OUTLOOK = "outlook"


@dataclass(frozen=True, slots=True)
class OAuthState:
    """CSRF state for OAuth PKCE flow.

    Stored in-memory with TTL for security. Contains the code_verifier
    needed to complete the PKCE exchange.
    """

    state: str
    provider: OAuthProvider
    redirect_uri: str
    code_verifier: str
    created_at: datetime
    expires_at: datetime

    def is_state_expired(self) -> bool:
        """Check if the OAuth state has expired."""
        return datetime.now(self.created_at.tzinfo) > self.expires_at


@dataclass(frozen=True, slots=True)
class OAuthTokens:
    """OAuth tokens returned from provider.

    Stored encrypted in IntegrationSecretModel via the repository.
    """

    access_token: str
    refresh_token: str | None
    token_type: str
    expires_at: datetime
    scope: str

    def is_expired(self) -> bool:
        """Check if the access token has expired."""
        return datetime.now(self.expires_at.tzinfo) > self.expires_at

    def to_secrets_dict(self) -> dict[str, str]:
        """Convert to dictionary for encrypted storage."""
        result: dict[str, str] = {
            OAUTH_FIELD_ACCESS_TOKEN: self.access_token,
            OAUTH_FIELD_TOKEN_TYPE: self.token_type,
            "expires_at": self.expires_at.isoformat(),
            OAUTH_FIELD_SCOPE: self.scope,
        }
        if self.refresh_token:
            result[OAUTH_FIELD_REFRESH_TOKEN] = self.refresh_token
        return result

    @classmethod
    def from_secrets_dict(cls, secrets: dict[str, str]) -> OAuthTokens:
        """Create from dictionary retrieved from encrypted storage.

        Args:
            secrets: Token dictionary from keystore.

        Returns:
            OAuthTokens with parsed expiration datetime.

        Raises:
            KeyError: If access_token or expires_at missing.
            ValueError: If expires_at is not valid ISO format.
        """
        # Parse expiration datetime from ISO string
        expires_at = datetime.fromisoformat(secrets["expires_at"])

        return cls(
            access_token=secrets[OAUTH_FIELD_ACCESS_TOKEN],
            refresh_token=secrets.get(OAUTH_FIELD_REFRESH_TOKEN),
            token_type=secrets.get(OAUTH_FIELD_TOKEN_TYPE, "Bearer"),
            expires_at=expires_at,
            scope=secrets.get(OAUTH_FIELD_SCOPE, ""),
        )
