"""Genesys Enhanced text-to-speech — billed in dollars, NOT in tokens.

A separate published page, a separate meter, a separate subtotal:
https://help.genesys.cloud/articles/genesys-enhanced-tts-pricing/
**last updated 2026-05-22**.

TTS never appears in the tokens-model article and is never converted to
tokens here. It is carried because Genesys AI spend is understated without
it — but it is reported as its own line, and the grand total shows tokens
and TTS as two labelled components rather than one blended figure.

Three published facts the obvious model misses:

* **"The monthly billing unit is one million characters, rounded up."**
  200,000 characters bills as $5; 1,000,005 characters bills as $10.
* **Free during virtual agents.** "Genesys Enhanced TTS is included free of
  charge when you use a virtual agent or agentic virtual agent during the
  interaction" — so deflection cuts this line as well as the token line.
* **Standard voices are ending.** End of sale 2026-03-29, end of support
  2026-08-05. Selecting Standard for a future-state model is a finding.
"""

from __future__ import annotations

import math
from dataclasses import dataclass

from .meters import Confidence
from .usage import DeflectionMix

TTS_SOURCE: str = "https://help.genesys.cloud/articles/genesys-enhanced-tts-pricing/"
TTS_SOURCE_DATE: str = "2026-05-22"

#: "$20 per one million TTS characters" (advanced: NTTS, neural, wavenet)
#: and "$5 per one million TTS characters" (standard).
TTS_PRICE_PER_MILLION_CHARS: dict[str, float] = {"advanced": 20.0, "standard": 5.0}

#: "The monthly billing unit is one million characters, rounded up."
TTS_BILLING_UNIT_CHARS: int = 1_000_000

#: Standard Enhanced TTS lifecycle — published end-of-sale / end-of-support.
TTS_STANDARD_END_OF_SALE: str = "2026-03-29"
TTS_STANDARD_END_OF_SUPPORT: str = "2026-08-05"

#: Published, so 🟢 — unlike the tokens article, this page states the rates.
TTS_CONFIDENCE: Confidence = Confidence.CONFIRMED

MONTHS_PER_YEAR: int = 12


@dataclass(frozen=True)
class TtsUsage:
    """Synthesised speech volume. Client data, from ``engagement-data``.

    "Usage includes all TTS characters, including letters, numbers,
    punctuation, and spaces."
    """

    calls_monthly: float
    chars_per_call: int = 2_000
    tier: str = "advanced"

    def __post_init__(self) -> None:
        if self.calls_monthly < 0:
            raise ValueError("calls_monthly must not be negative")
        if self.chars_per_call < 0:
            raise ValueError("chars_per_call must not be negative")
        if self.tier not in TTS_PRICE_PER_MILLION_CHARS:
            raise ValueError(
                f"unknown TTS tier {self.tier!r}; known: "
                f"{', '.join(sorted(TTS_PRICE_PER_MILLION_CHARS))}"
            )


@dataclass(frozen=True)
class TtsLine:
    """The TTS bill, reported separately from the token bill."""

    tier: str
    rate_per_million_chars: float
    total_chars_monthly: float
    free_share: float
    free_chars_monthly: float
    billable_chars_monthly: float
    billed_millions_monthly: int
    cost_monthly: float
    cost_annual: float
    confidence: Confidence
    source_url: str
    source_date: str
    warnings: tuple[str, ...] = ()


def tts_free_share(mix: DeflectionMix) -> float:
    """Share of TTS characters Genesys does not charge for.

    "included free of charge when you use a virtual agent or agentic virtual
    agent during the interaction". Reuses the same partition as the token
    model, so the two lines cannot disagree about how much was deflected.
    """
    return min(1.0, mix.virtual_agent_share + mix.agentic_va_share)


def tts_cost(
    usage: TtsUsage, mix: DeflectionMix | None = None, concession_pct: float = 0.0
) -> TtsLine:
    """The monthly and annual TTS bill, with the published per-1M round-up.

    The round-up lands on the *billable* character total each month, after
    the virtual-agent free share is removed.
    """
    if not 0.0 <= concession_pct < 1.0:
        raise ValueError("concession_pct must be in [0, 1)")

    free_share = tts_free_share(mix) if mix is not None else 0.0
    total_chars = usage.calls_monthly * usage.chars_per_call
    free_chars = total_chars * free_share
    billable_chars = total_chars - free_chars

    billed_millions = math.ceil(billable_chars / TTS_BILLING_UNIT_CHARS)
    rate = TTS_PRICE_PER_MILLION_CHARS[usage.tier]
    monthly = billed_millions * rate * (1.0 - concession_pct)

    warnings: list[str] = []
    if usage.tier == "standard":
        warnings.append(
            "Standard Enhanced TTS reached end of sale on "
            f"{TTS_STANDARD_END_OF_SALE} and end of support on "
            f"{TTS_STANDARD_END_OF_SUPPORT} — price a future state on "
            "advanced voices."
        )

    return TtsLine(
        tier=usage.tier,
        rate_per_million_chars=rate,
        total_chars_monthly=total_chars,
        free_share=free_share,
        free_chars_monthly=free_chars,
        billable_chars_monthly=billable_chars,
        billed_millions_monthly=billed_millions,
        cost_monthly=monthly,
        cost_annual=monthly * MONTHS_PER_YEAR,
        confidence=TTS_CONFIDENCE,
        source_url=TTS_SOURCE,
        source_date=TTS_SOURCE_DATE,
        warnings=tuple(warnings),
    )
