"""
DVGateway SDK — Public Type Definitions

Core types for interacting with the DVGateway real-time voice media gateway.
All adapters and pipeline components are built on these interfaces.
"""

from __future__ import annotations

import abc
from dataclasses import dataclass, field
from datetime import datetime
from typing import (
    Any,
    AsyncIterable,
    Callable,
    Generic,
    Literal,
    Protocol,
    TypeVar,
)

# ─── Authentication ────────────────────────────────────────────────────────


@dataclass(frozen=True)
class ApiKeyAuth:
    type: Literal["apiKey"] = "apiKey"
    api_key: str = ""


@dataclass(frozen=True)
class JwtAuth:
    type: Literal["jwt"] = "jwt"
    token: str = ""


Auth = ApiKeyAuth | JwtAuth

# ─── Reconnection ──────────────────────────────────────────────────────────


@dataclass
class ReconnectOptions:
    """WebSocket reconnection strategy."""

    max_attempts: int = 10
    initial_delay_ms: int = 2000
    max_delay_ms: int = 30_000
    backoff_multiplier: float = 2.0
    on_reconnect: Callable[[int], None] | None = None
    on_max_attempts_reached: Callable[[], None] | None = None


# ─── Security ─────────────────────────────────────────────────────────────


@dataclass
class MtlsOptions:
    cert: bytes = b""
    key: bytes = b""
    ca: bytes | None = None


@dataclass
class SecurityOptions:
    mask_pii: bool = True
    mtls: MtlsOptions | None = None
    force_tls: bool = True


# ─── Client Options ────────────────────────────────────────────────────────


@dataclass
class DVGatewayOptions:
    """Options for creating a DVGatewayClient instance."""

    base_url: str = "http://localhost:8080"
    auth: Auth = field(default_factory=lambda: ApiKeyAuth(api_key="dev-no-auth"))
    tenant_id: str | None = None
    timeout: int = 30_000
    reconnect: ReconnectOptions = field(default_factory=ReconnectOptions)
    security: SecurityOptions = field(default_factory=SecurityOptions)
    logger: Logger | None = None


# ─── Session ──────────────────────────────────────────────────────────────

StreamDir = Literal["both", "in", "out"]


@dataclass
class CallSession:
    """A 1:1 call session."""

    linked_id: str = ""
    conf_id: str | None = None
    dir: StreamDir = "both"
    caller: str | None = None
    """Caller phone number (CALLERID(num)), e.g. '01012345678'."""
    caller_name: str | None = None
    """Caller display name (CALLERID(name)), e.g. '홍길동'."""
    callee: str | None = None
    """Called number (EXTEN / B-leg), e.g. '025001000'."""
    did: str | None = None
    """DID (Direct Inward Dialing) main number."""
    call_id: str | None = None
    """Business system call ID (from ARI args)."""
    agent_number: str | None = None
    """Agent extension number."""
    started_at: datetime = field(default_factory=datetime.now)
    stream_url: str = ""
    tenant_id: str | None = None
    custom_value_1: str | None = None
    """User-defined custom variable 1 (dialplan CUSTOM_VALUE_01)."""
    custom_value_2: str | None = None
    """User-defined custom variable 2 (dialplan CUSTOM_VALUE_02)."""
    custom_value_3: str | None = None
    """User-defined custom variable 3 (dialplan CUSTOM_VALUE_03)."""
    metadata: dict[str, str] | None = None


@dataclass
class ConferenceParticipant:
    linked_id: str = ""
    caller: str | None = None
    joined_at: datetime = field(default_factory=datetime.now)


@dataclass
class ConferenceSession:
    conf_id: str = ""
    tenant_id: str | None = None
    participants: list[ConferenceParticipant] = field(default_factory=list)
    started_at: datetime = field(default_factory=datetime.now)
    metadata: dict[str, str] | None = None


# ─── Call Events ──────────────────────────────────────────────────────────


@dataclass(frozen=True)
class CallNewEvent:
    type: Literal["call:new"] = "call:new"
    session: CallSession = field(default_factory=CallSession)
    tenant_id: str | None = None
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass(frozen=True)
class CallEndedEvent:
    type: Literal["call:ended"] = "call:ended"
    linked_id: str = ""
    duration_sec: float = 0.0
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass(frozen=True)
class ConfJoinEvent:
    type: Literal["conf:join"] = "conf:join"
    linked_id: str = ""
    conf_id: str = ""
    caller: str | None = None
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass(frozen=True)
class ConfLeaveEvent:
    type: Literal["conf:leave"] = "conf:leave"
    linked_id: str = ""
    conf_id: str = ""
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass(frozen=True)
class ConfEndedEvent:
    type: Literal["conf:ended"] = "conf:ended"
    conf_id: str = ""
    timestamp: datetime = field(default_factory=datetime.now)


CallEvent = CallNewEvent | CallEndedEvent | ConfJoinEvent | ConfLeaveEvent | ConfEndedEvent

# ─── Audio ────────────────────────────────────────────────────────────────


@dataclass(frozen=True)
class RawAudioChunk:
    """Raw audio frame as received from the gateway (slin16 PCM)."""

    linked_id: str = ""
    dir: Literal["in", "out", "mixed"] = "mixed"
    raw: bytes = b""
    timestamp_ms: float = 0.0


@dataclass(frozen=True)
class AudioChunk:
    """Normalized audio frame for AI adapters."""

    linked_id: str = ""
    dir: Literal["in", "out", "mixed"] = "mixed"
    samples: list[float] = field(default_factory=list)
    sample_rate: int = 16000
    duration_ms: float = 0.0
    timestamp_ms: float = 0.0


# ─── Transcription ────────────────────────────────────────────────────────


@dataclass(frozen=True)
class TranscriptWord:
    word: str = ""
    start_ms: float = 0.0
    end_ms: float = 0.0
    confidence: float | None = None


@dataclass(frozen=True)
class SentimentResult:
    """Sentiment analysis result from STT provider."""

    sentiment: Literal["positive", "neutral", "negative"] = "neutral"
    """Segment-level sentiment: positive, neutral, or negative."""
    sentiment_score: float = 0.0
    """Sentiment confidence score (0.0–1.0)."""


@dataclass(frozen=True)
class TranscriptResult:
    linked_id: str = ""
    text: str = ""
    is_final: bool = False
    speaker: str | None = None
    confidence: float | None = None
    language: str | None = None
    words: list[TranscriptWord] | None = None
    timestamp_ms: float = 0.0
    sentiment: SentimentResult | None = None
    """Sentiment analysis result. Present when sentiment is enabled on the STT adapter."""


# ─── TTS ──────────────────────────────────────────────────────────────────


@dataclass
class HumanVoiceOptions:
    """Human-like voice optimization options.

    Controls how natural and human-like the synthesized speech sounds.
    Each TTS provider maps these options to its own API parameters:

    - ElevenLabs: stability ↓, style ↑, multilingual model selection
    - OpenAI gpt-4o-mini-tts: voiceInstructions with natural speech directives

    Default preset is optimized for Korean (ko-KR) natural conversational speech.
    """

    natural_pauses: bool = True
    """Enable natural pauses between sentences and clauses."""
    breathing_sounds: bool = True
    """Include subtle breathing sounds between phrases."""
    filler_words: bool = True
    """Allow filler words (Korean: 음, 어, 그 / English: um, uh, well)."""
    exclamations: bool = True
    """Natural exclamations (Korean: 아, 네, 맞아요 / English: oh, right, I see)."""
    emotional_range: float = 0.6
    """Emotional expressiveness range (0.0–1.0). ElevenLabs → style parameter."""
    speech_variation: float = 0.7
    """Speech variation / naturalness (0.0–1.0). ElevenLabs → 1 - stability."""


HUMAN_VOICE_DEFAULTS_KO = HumanVoiceOptions(
    natural_pauses=True,
    breathing_sounds=True,
    filler_words=True,
    exclamations=True,
    emotional_range=0.6,
    speech_variation=0.7,
)
"""Default HumanVoiceOptions optimized for Korean conversational speech."""

HUMAN_VOICE_DEFAULTS_EN = HumanVoiceOptions(
    natural_pauses=True,
    breathing_sounds=True,
    filler_words=False,
    exclamations=True,
    emotional_range=0.5,
    speech_variation=0.6,
)
"""HumanVoiceOptions optimized for English conversational speech."""


@dataclass
class VoiceInfo:
    """Voice metadata returned from voice fetch APIs."""

    id: str = ""
    label: str = ""


@dataclass
class TtsOptions:
    voice_id: str | None = None
    speed: float | None = None
    pitch: float | None = None
    language: str | None = None
    human_voice: HumanVoiceOptions | None = None
    """Human-like voice optimization. Default: Korean-optimized preset."""


# ─── LLM ──────────────────────────────────────────────────────────────────


@dataclass(frozen=True)
class Message:
    role: Literal["system", "user", "assistant"] = "user"
    content: str = ""


@dataclass
class LlmOptions:
    model: str | None = None
    system_prompt: str | None = None
    max_tokens: int | None = None
    temperature: float | None = None


# ─── AI Adapter Interfaces ────────────────────────────────────────────────


class SttAdapter(abc.ABC):
    """Speech-to-Text adapter interface."""

    @abc.abstractmethod
    async def start_stream(self, linked_id: str, audio_stream: AsyncIterable[AudioChunk]) -> None:
        """Begin streaming audio for transcription."""

    @abc.abstractmethod
    def on_transcript(self, handler: Callable[[TranscriptResult], None]) -> None:
        """Register a handler for transcription results."""

    @abc.abstractmethod
    async def stop(self) -> None:
        """Stop the stream and release resources."""


class TtsAdapter(abc.ABC):
    """Text-to-Speech adapter interface."""

    @abc.abstractmethod
    def synthesize(self, text: str, options: TtsOptions | None = None) -> AsyncIterable[bytes]:
        """Synthesize text to speech. Returns async iterable of 16kHz slin16 PCM chunks."""


class LlmAdapter(abc.ABC):
    """Large Language Model adapter interface."""

    @abc.abstractmethod
    def chat(self, messages: list[Message], options: LlmOptions | None = None) -> AsyncIterable[str]:
        """Generate a streaming chat completion. Returns async iterable of text tokens."""


# ─── Pipeline ─────────────────────────────────────────────────────────────


@dataclass
class PipelineOptions:
    dir: StreamDir = "both"
    end_of_utterance_silence_ms: int = 500
    submit_to_minutes: bool = True


# ─── Logging ──────────────────────────────────────────────────────────────


class Logger(Protocol):
    """Logger interface — compatible with any structured logger."""

    def debug(self, ctx: dict[str, Any], msg: str) -> None: ...
    def info(self, ctx: dict[str, Any], msg: str) -> None: ...
    def warn(self, ctx: dict[str, Any], msg: str) -> None: ...
    def error(self, ctx: dict[str, Any], msg: str) -> None: ...


# ─── Utility ──────────────────────────────────────────────────────────────

Unsubscribe = Callable[[], None]

T = TypeVar("T")
E = TypeVar("E", bound=Exception)


@dataclass(frozen=True)
class Ok(Generic[T]):
    value: T
    ok: Literal[True] = True


@dataclass(frozen=True)
class Err(Generic[E]):
    error: E
    ok: Literal[False] = False


Result = Ok[T] | Err[E]
