"""
PUT /onboarding/training-activation request schema — PRD-HWONB-010.

This step has no prototype precedent — built from scratch per PRD-HWONB-010
§2/§4. CERT findings baked in here (C-18/C-19, PRD-HWONB-001 §13.1):

  - C-18: the GP lookup (`GET /product/trainingAndActivationDetail`) returns
    `{trainingAndActivationDetail:{merchantTrainedBy:[TransFirst,Agent],
    equipmentShippedTo:[DBA,Legal,Agent,Other,NA],
    welcomeKitEmailedTo:[DBA,Legal,Agent,Other,NA]}}`. The WRITE fields use
    different key names (`merTrainedBy`/`equipShippedTo`/`welcomeKitEmailTo`)
    but the SAME word values as the lookup (`NA`/`DBA`/... — NOT short codes
    like `AGT`/`LEG`/`OTH`/`N/A`, which an earlier PRD draft wrongly assumed).
  - C-19: `equipShippedTo="NA"` was accepted (201) even with a standalone
    (CP) product already on the application — GP does NOT enforce "reject
    NA when equipment exists" at section-save time. Per the PRD, this is
    kept as a client-side UX guard only (see `products_environment` note on
    `TrainingActivationSectionRequest`), never a hard server-side rejection.
"""

from __future__ import annotations

from typing import Optional

from pydantic import BaseModel, Field, model_validator

from src.apps.merchant_onboarding.helpers.field_mapping import (
    TRAINING_ACTIVATION_VALUE_CHOICES,
    TRAINING_MERCHANT_TRAINED_BY_CHOICES,
)

# Re-exported as Literal-friendly tuples for schema use — kept in
# helpers/field_mapping.py as the single source of truth (PRD-HWONB-001 §13.1).
MER_TRAINED_BY_CHOICES = TRAINING_MERCHANT_TRAINED_BY_CHOICES  # ("TransFirst", "Agent")
SHIP_EMAIL_CHOICES = TRAINING_ACTIVATION_VALUE_CHOICES  # ("DBA", "Legal", "Agent", "Other", "NA")


class TrainingActivationRequest(BaseModel):
    """
    PRD-HWONB-010 §2 — request body for `PUT /onboarding/training-activation`.

    Field names here are the GP WRITE field names (`merTrainedBy`,
    `welcomeKitEmailTo`, `equipShippedTo`) — do not confuse with the GET
    lookup's differently-cased/named keys (`merchantTrainedBy`,
    `welcomeKitEmailedTo`, `equipmentShippedTo`), which are read-only
    reference data for populating the three dropdowns (§3).
    """

    merTrainedBy: str = Field(..., description="Who trains the merchant: TransFirst | Agent.")
    welcomeKitEmailTo: str = Field(..., description="Who receives the welcome-kit email: DBA|Legal|Agent|Other|NA.")
    equipShippedTo: str = Field(..., description="Where physical equipment ships: DBA|Legal|Agent|Other|NA.")

    # Required only when equipShippedTo == "Other" (PRD §2 uses "OTH" as the
    # conditional trigger in prose/UI spec, but the CERT-confirmed write
    # value set is the word form "Other" — §2/C-18. Trigger on "Other".)
    name: Optional[str] = Field(default=None, max_length=35)
    addressLine1: Optional[str] = Field(default=None, max_length=35)
    city: Optional[str] = Field(default=None, max_length=35)
    state: Optional[str] = Field(default=None, min_length=2, max_length=2)
    # AC-4 — payload field name sent is `zipCode`, never `zip` (a one-off
    # sample typo in the PDF's own sample response — every real field table
    # and Postman request/response body uses `zipCode`).
    zipCode: Optional[str] = Field(default=None, min_length=5, max_length=5)

    # Not part of the GP payload — client-supplied context so this schema
    # can apply the §6/AC-3 "hide NA when CP equipment exists" UX guard
    # server-side too (defense in depth, even though C-19 confirmed GP
    # itself does not hard-block "NA" with equipment present at this stage).
    # Optional so callers who don't know it yet aren't forced to send it;
    # the guard below only fires when it's explicitly provided as "CP".
    products_environment: Optional[str] = Field(
        default=None,
        exclude=True,
        description="Informational only (not sent to GP) — 'CP' or 'CNP', mirrors products.environment.",
    )

    @model_validator(mode="after")
    def _validate_conditionals(self) -> "TrainingActivationRequest":
        if self.merTrainedBy not in MER_TRAINED_BY_CHOICES:
            raise ValueError(f"merTrainedBy must be one of {MER_TRAINED_BY_CHOICES}.")
        if self.welcomeKitEmailTo not in SHIP_EMAIL_CHOICES:
            raise ValueError(f"welcomeKitEmailTo must be one of {SHIP_EMAIL_CHOICES}.")
        if self.equipShippedTo not in SHIP_EMAIL_CHOICES:
            raise ValueError(f"equipShippedTo must be one of {SHIP_EMAIL_CHOICES}.")

        # PRD §6/AC-3 — UX guard only, NOT a hard API rule (C-19 confirmed
        # GP accepts "NA" even with CP equipment present). Enforced here as
        # a soft validation so a merchant who somehow bypasses the disabled
        # dropdown option client-side still gets a clear local error, but
        # this must never be marketed as an authoritative GP rule.
        if self.equipShippedTo == "NA" and self.products_environment == "CP":
            raise ValueError(
                "equipShippedTo cannot be 'NA' when the merchant's environment is CP "
                "(physical equipment expected) — this is a HubWallet UX guard, not a "
                "GP-enforced rule (C-19)."
            )

        if self.equipShippedTo == "Other":
            missing = [
                field_name
                for field_name in ("name", "addressLine1", "city", "state", "zipCode")
                if not getattr(self, field_name)
            ]
            if missing:
                raise ValueError(
                    f"The following fields are required when equipShippedTo='Other': {', '.join(missing)}."
                )
        return self

    def to_gp_payload(self) -> dict:
        """
        Build the exact GP write body — flat object, no reshaping needed
        (PRD §5). Excludes `products_environment` (local-only) and any
        None-valued conditional address fields.
        """
        data = self.model_dump(exclude={"products_environment"}, exclude_none=True)
        return data
