"""Pydantic schemas для own_planned_project (#1169 PR1, ТЗ §25.3).

OwnPlannedProjectCreate  — тело POST (name обязателен).
OwnPlannedProjectUpdate  — тело PUT (все поля опциональны — partial update).
OwnPlannedProjectOut     — ответ API (включает id, created_by, created_at, updated_at).

created_by НЕ принимается из тела — проставляется роутером из X-Authenticated-User
(зеркало insight #948). Это «наш будущий/планируемый проект» приватного пайплайна:
второй источник §25.3-движка каннибализации (парный к «текущим» из domrf).
"""

from __future__ import annotations

from datetime import date, datetime
from typing import Literal

from pydantic import BaseModel, Field, field_validator, model_validator

# Класс проекта — мягкий enum (зеркалит class-вокабуляр forecasting SegmentSpec
# и domrf obj_class). None допустим (класс может быть ещё не определён). Матчинг в
# PR2 регистронезависимый, поэтому здесь фиксируем «человеческий» Title-case-набор.
OwnProjectClass = Literal["Стандарт", "Комфорт", "Бизнес", "Премиум"]


def _validate_unit_mix(value: dict[str, float] | None) -> dict[str, float] | None:
    """Проверить, что доли квартирографии — числа в [0,1]. None допустим.

    Сумму ≈1 НЕ навязываем жёстко (округления / нежилая часть) — зеркалит CHECK
    миграции 148. Возвращает значение как есть (после проверки).
    """
    if value is None:
        return None
    for key, share in value.items():
        if not isinstance(share, int | float) or isinstance(share, bool):
            raise ValueError(
                f"unit_mix['{key}'] должно быть числом, получено {type(share).__name__}"
            )
        if share < 0 or share > 1:
            raise ValueError(f"unit_mix['{key}']={share} вне диапазона [0,1]")
    return value


class OwnPlannedProjectCreate(BaseModel):
    name: str = Field(..., min_length=1, max_length=300, description="Название проекта/ЖК")
    district: str | None = Field(None, max_length=200, description="Район (location ref)")
    obj_class: OwnProjectClass | None = Field(
        None, description="Класс: Стандарт|Комфорт|Бизнес|Премиум"
    )
    planned_release_month: date | None = Field(
        None, description="Планируемый месяц выхода в продажу (нормализуется к 1-му числу)"
    )
    price_min_per_m2: float | None = Field(
        None, ge=0, description="Нижняя граница цены, ₽/м² (≥0)"
    )
    price_max_per_m2: float | None = Field(
        None, ge=0, description="Верхняя граница цены, ₽/м² (≥0)"
    )
    unit_mix: dict[str, float] | None = Field(
        None,
        description='Квартирография: доли по форматам {"studio":0.3,"1k":0.4,...} (каждая в [0,1])',
    )
    lon: float | None = Field(None, ge=-180.0, le=180.0, description="Долгота WGS-84 (центроид)")
    lat: float | None = Field(None, ge=-90.0, le=90.0, description="Широта WGS-84 (центроид)")

    @field_validator("unit_mix")
    @classmethod
    def _check_unit_mix(cls, value: dict[str, float] | None) -> dict[str, float] | None:
        return _validate_unit_mix(value)

    @model_validator(mode="after")
    def _check_price_range(self) -> OwnPlannedProjectCreate:
        if (
            self.price_min_per_m2 is not None
            and self.price_max_per_m2 is not None
            and self.price_min_per_m2 > self.price_max_per_m2
        ):
            raise ValueError("price_min_per_m2 не может быть больше price_max_per_m2")
        return self


class OwnPlannedProjectUpdate(BaseModel):
    """Partial update (PUT) — переданные поля заменяются, отсутствующие не трогаются."""

    name: str | None = Field(None, min_length=1, max_length=300)
    district: str | None = Field(None, max_length=200)
    obj_class: OwnProjectClass | None = None
    planned_release_month: date | None = None
    price_min_per_m2: float | None = Field(None, ge=0)
    price_max_per_m2: float | None = Field(None, ge=0)
    unit_mix: dict[str, float] | None = None
    lon: float | None = Field(None, ge=-180.0, le=180.0)
    lat: float | None = Field(None, ge=-90.0, le=90.0)

    @field_validator("unit_mix")
    @classmethod
    def _check_unit_mix(cls, value: dict[str, float] | None) -> dict[str, float] | None:
        return _validate_unit_mix(value)


class OwnPlannedProjectOut(BaseModel):
    id: int
    created_by: str
    name: str
    district: str | None
    obj_class: str | None
    planned_release_month: date | None
    price_min_per_m2: float | None
    price_max_per_m2: float | None
    unit_mix: dict[str, float] | None
    lon: float | None
    lat: float | None
    created_at: datetime
    updated_at: datetime


class OwnPlannedProjectList(BaseModel):
    """Ответ list-эндпоинта: total + страница строк (как insight)."""

    total: int
    limit: int
    offset: int
    rows: list[OwnPlannedProjectOut]
