"""Repository-backed petstore operations used by generated HTTP protocol adapters."""

from __future__ import annotations

import json
from datetime import UTC, datetime
from typing import Any

from generated.petstore.api.category_detail import CategoryDetail
from generated.petstore.api.category_summary import CategorySummary
from generated.petstore.api.create_pet_input import CreatePetInput
from generated.petstore.api.fulfillment_state import (
    FulfillmentState as ApiFulfillmentState,
)
from generated.petstore.api.fulfillment_state import (
    FulfillmentStateDelivered,
    FulfillmentStatePending,
    FulfillmentStateShipped,
)
from generated.petstore.api.order_detail import OrderDetail
from generated.petstore.api.order_line_detail import OrderLineDetail
from generated.petstore.api.order_priority import OrderPriority
from generated.petstore.api.order_status import OrderStatus
from generated.petstore.api.owner_summary import OwnerSummary
from generated.petstore.api.pet_attribute import PetAttribute
from generated.petstore.api.pet_attribute_value import (
    PetAttributeValue,
    PetAttributeValueColor,
    PetAttributeValueVaccinated,
    PetAttributeValueWeight_kg,
)
from generated.petstore.api.pet_detail import PetDetail
from generated.petstore.api.pet_profile_summary import PetProfileSummary
from generated.petstore.api.pet_species import PetSpecies
from generated.petstore.api.pet_status import PetStatus
from generated.petstore.api.place_order_input import PlaceOrderInput
from generated.petstore.api.postal_address import PostalAddress
from generated.petstore.api.store_summary import StoreSummary
from generated.petstore.api.update_pet_body import UpdatePetBody
from generated.petstore.db.models.order_repository_models import FulfillmentState as DbFulfillmentState
from generated.petstore.db.models.order_repository_models import (
    FulfillmentStateDelivered as DbFulfillmentStateDelivered,
)
from generated.petstore.db.models.order_repository_models import FulfillmentStatePending as DbFulfillmentStatePending
from generated.petstore.db.models.order_repository_models import FulfillmentStateShipped as DbFulfillmentStateShipped
from generated.petstore.db.models.pet_repository_models import PetHighlight as GeneratedPetHighlight
from generated.petstore.db.models.pet_repository_models import PetTags as GeneratedPetTags
from generated.petstore.db.order_priority import OrderPriority as DbOrderPriority
from generated.petstore.db.order_status import OrderStatus as DbOrderStatus
from generated.petstore.db.pet_species import PetSpecies as DbPetSpecies
from generated.petstore.db.pet_status import PetStatus as DbPetStatus
from server.database import RepositoryBundle


def _postal_from_generated(address: Any) -> PostalAddress:
    return PostalAddress(
        street=address.street,
        city=address.city,
        postal_code=address.postal_code,
    )


def _fulfillment_to_api(fulfillment: DbFulfillmentState) -> ApiFulfillmentState:
    if isinstance(fulfillment, DbFulfillmentStatePending):
        return FulfillmentStatePending(pending=fulfillment.pending)
    if isinstance(fulfillment, DbFulfillmentStateShipped):
        return FulfillmentStateShipped(shipped=fulfillment.shipped)
    if isinstance(fulfillment, DbFulfillmentStateDelivered):
        return FulfillmentStateDelivered(delivered=fulfillment.delivered)
    raise ValueError(f"unsupported fulfillment variant: {fulfillment!r}")


def _attribute_value_from_union(value: PetAttributeValue) -> GeneratedPetHighlight:
    """Persist the union variant as a (discriminator name, text value) pair in PetHighlight."""
    if isinstance(value, PetAttributeValueColor):
        return GeneratedPetHighlight(name="color", color=value.color)
    if isinstance(value, PetAttributeValueWeight_kg):
        return GeneratedPetHighlight(name="weight_kg", color=str(value.weight_kg))
    if isinstance(value, PetAttributeValueVaccinated):
        return GeneratedPetHighlight(
            name="vaccinated",
            color="true" if value.vaccinated else "false",
        )
    raise ValueError(f"unsupported PetAttributeValue variant: {value!r}")


def _attribute_value_to_union(attribute: GeneratedPetHighlight) -> PetAttributeValue:
    """Reconstruct the union variant from the discriminator persisted in PetHighlight.name."""
    match attribute.name:
        case "color":
            return PetAttributeValueColor(color=attribute.color)
        case "weight_kg":
            return PetAttributeValueWeight_kg(weight_kg=float(attribute.color))
        case "vaccinated":
            return PetAttributeValueVaccinated(vaccinated=attribute.color == "true")
        case other:
            raise ValueError(f"unsupported PetHighlight discriminator: {other!r}")


class PetstoreRepositoryService:
    def __init__(self, repositories: RepositoryBundle) -> None:
        self._repositories = repositories

    async def create_pet(self, request: CreatePetInput) -> str:
        adopted_at = request.adopted_at or datetime.now(tz=UTC)
        featured = _attribute_value_from_union(request.attributes[0].value)
        created = await self._repositories.pets.create_pet_record(
            name=request.name,
            status=DbPetStatus(request.status.value),
            species=DbPetSpecies(request.species.value),
            category_id=request.category_id,
            owner_id=request.owner_id or "",
            tag_count=request.tag_count,
            tags=GeneratedPetTags(items=request.tags),
            featured_attribute=featured,
            photo=request.photo or b"",
            adopted_at=adopted_at,
        )
        return created.id

    async def get_pet(self, pet_id: str) -> PetDetail | None:
        record = await self._repositories.pets.get_pet_record(id=pet_id)
        if record is None:
            return None
        profile = record.pet_profiles[0] if record.pet_profiles else None
        owner = (
            OwnerSummary(
                id=record.owner.id,
                full_name=record.owner.full_name,
                mailing_address=_postal_from_generated(record.owner.mailing_address),
                created_at=record.owner.created_at,
            )
            if record.owner is not None
            else None
        )
        return PetDetail(
            id=record.id,
            name=record.name,
            status=PetStatus(record.status.value),
            species=PetSpecies(record.species.value),
            category_id=record.category_id,
            owner_id=record.owner_id or None,
            tag_count=record.tag_count,
            tags=list(record.tags.items),
            attributes=[
                PetAttribute(
                    name=record.featured_attribute.name,
                    value=_attribute_value_to_union(record.featured_attribute),
                )
            ],
            photo=record.photo or None,
            metadata=None,
            adopted_at=record.adopted_at,
            created_at=record.created_at,
            updated_at=record.updated_at,
            category=CategorySummary(
                id=record.category.id,
                name=record.category.name,
                store_id=record.category.store_id,
            ),
            store=StoreSummary(id=record.store.id, name=record.store.name),
            owner=owner,
            profile=(
                PetProfileSummary(id=profile.id, biography=profile.biography, pet_id=profile.pet_id)
                if profile is not None
                else None
            ),
        )

    async def update_pet(self, pet_id: str, request: UpdatePetBody) -> bool:
        adopted_at = request.adopted_at or datetime.now(tz=UTC)
        featured = _attribute_value_from_union(request.attributes[0].value)
        updated = await self._repositories.pets.update_pet_record(
            name=request.name,
            status=DbPetStatus(request.status.value),
            species=DbPetSpecies(request.species.value),
            category_id=request.category_id,
            owner_id=request.owner_id or "",
            tag_count=request.tag_count,
            tags=GeneratedPetTags(items=request.tags),
            featured_attribute=featured,
            photo=request.photo or b"",
            adopted_at=adopted_at,
            id=pet_id,
        )
        return updated.updated

    async def delete_pet(self, pet_id: str) -> bool:
        deleted = await self._repositories.pets.delete_pet_record(id=pet_id)
        return deleted.deleted

    async def get_category(self, category_id: str) -> CategoryDetail | None:
        record = await self._repositories.categories.get_category_record(id=category_id)
        if record is None:
            return None
        return CategoryDetail(
            id=record.id,
            name=record.name,
            store_id=record.store_id,
            store=StoreSummary(id=record.store.id, name=record.store.name),
        )

    async def place_order(self, request: PlaceOrderInput) -> str:
        created = await self._repositories.orders.create_order_record(
            label=request.label,
            status=DbOrderStatus(request.status.value),
            priority=DbOrderPriority(request.priority.value),
        )
        return created.id

    async def get_order(self, order_id: str) -> OrderDetail | None:
        record = await self._repositories.orders.get_order_record(id=order_id)
        if record is None:
            return None
        return OrderDetail(
            id=record.id,
            label=record.label,
            status=OrderStatus(record.status.value),
            priority=OrderPriority(record.priority.value),
            created_at=record.created_at,
            updated_at=record.updated_at,
            lines=[
                OrderLineDetail(
                    id=line.id,
                    order_id=line.order_id,
                    pet_id=line.pet_id,
                    quantity=line.quantity,
                    unit_price_cents=line.unit_price_cents,
                    fulfillment=_fulfillment_to_api(line.fulfillment),
                )
                for line in record.order_lines
            ],
        )

    async def seed_reference_data(self) -> tuple[str, str]:
        store_id = await self._seed_store()
        category = await self._repositories.categories.create_category_record(
            name="Dogs",
            store_id=store_id,
        )
        return store_id, category.id

    async def seed_fulfillment_orders(self) -> dict[str, str]:
        """Seed one single-line order per FulfillmentState variant; return their ids by variant."""
        shipped_at = datetime(2026, 1, 2, 3, 4, 5, tzinfo=UTC).isoformat()
        delivered_at = datetime(2026, 2, 3, 4, 5, 6, tzinfo=UTC).isoformat()
        variants: dict[str, dict[str, str]] = {
            "pending": {"pending": "awaiting-stock"},
            "shipped": {"shipped": shipped_at},
            "delivered": {"delivered": delivered_at},
        }
        order_ids: dict[str, str] = {}
        for variant, fulfillment in variants.items():
            order_id = await self._seed_single_line_order(
                label=f"{variant}-order",
                fulfillment=fulfillment,
            )
            order_ids[variant] = order_id
        return order_ids

    async def _seed_single_line_order(self, label: str, fulfillment: dict[str, str]) -> str:
        connection = self._repositories.connection
        order_cursor = await connection.execute(
            "INSERT INTO orders (label, status, priority) VALUES (?, ?, ?) RETURNING id;",
            (label, "placed", 1),
        )
        order_row = await order_cursor.fetchone()
        if order_row is None:
            raise RuntimeError(f"failed to seed order {label!r}")
        order_id = str(order_row[0])
        await connection.execute(
            "INSERT INTO order_lines "
            "(order_id, pet_id, quantity, unit_price_cents, fulfillment) "
            "VALUES (?, ?, ?, ?, ?);",
            (order_id, "seed-pet", 1, 1999, json.dumps(fulfillment)),
        )
        await connection.commit()
        return order_id

    async def _seed_store(self) -> str:
        connection = self._repositories.connection
        cursor = await connection.execute(
            "INSERT INTO stores (name) VALUES (?) RETURNING id;",
            ("Demo Store",),
        )
        row = await cursor.fetchone()
        if row is None:
            raise RuntimeError("failed to seed store")
        await connection.commit()
        return str(row[0])
