"""
Real Estate data models for Architektur-Planungs-App.
Implements a general Swiss architecture planning data model.
(PEK is one example implementation, but the model is general-purpose)
"""

from typing import List, Dict, Any, Optional, ForwardRef
from enum import Enum
from pydantic import BaseModel, Field
from modules.shared.attributeUtils import registerModelLabels
from modules.shared.timeUtils import getUtcTimestamp
import uuid

# ===== Enums =====

class StatusProzess(str, Enum):
    """Project process status"""
    EINGANG = "Eingang"
    ANALYSE = "Analyse"
    STUDIE = "Studie"
    PLANUNG = "Planung"
    BAURECHTSVERFAHREN = "Baurechtsverfahren"
    UMSETZUNG = "Umsetzung"
    ARCHIV = "Archiv"


class DokumentTyp(str, Enum):
    """Document type for categorization"""
    KANTON_BAUREGLEMENT_AKTUELL = "kantonBaureglementAktuell"
    KANTON_BAUREGLEMENT_REVISION = "kantonBaureglementRevision"
    KANTON_BAUVERORDNUNG_AKTUELL = "kantonBauverordnungAktuell"
    KANTON_BAUVERORDNUNG_REVISION = "kantonBauverordnungRevision"
    GEMEINDE_BZO_AKTUELL = "gemeindeBzoAktuell"
    GEMEINDE_BZO_REVISION = "gemeindeBzoRevision"


class JaNein(str, Enum):
    """Three-valued state for optional yes/no questions"""
    UNBEKANNT = ""  # Empty string for unknown/not captured
    JA = "Ja"
    NEIN = "Nein"


class GeoTag(str, Enum):
    """Geopoint categories"""
    K1 = "K1"  # Fixpunkt höchster Genauigkeit
    K2 = "K2"  # Fixpunkt mittlerer Genauigkeit
    K3 = "K3"  # Fixpunkt niedriger Genauigkeit
    GEOMETER = "Geometer"  # Vom Geometer vermessener Punkt


# ===== Helper Models (must be defined before main models) =====

class GeoPunkt(BaseModel):
    """Represents a 3D point with reference."""
    koordinatensystem: str = Field(
        description="Coordinate system (e.g. 'LV95', 'EPSG:2056')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    x: float = Field(
        description="East value (E) [m], typically 2'480'000 - 2'840'000",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=True,
    )
    y: float = Field(
        description="North value (N) [m], typically 1'070'000 - 1'300'000",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=True,
    )
    z: Optional[float] = Field(
        None,
        description="Height above sea level [m]",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    referenz: Optional[GeoTag] = Field(
        None,
        description="Point categorization",
        frontend_type="select",
        frontend_readonly=False,
        frontend_required=False,
    )


class GeoPolylinie(BaseModel):
    """Represents a line or polygon from multiple GeoPunkte."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
    )
    closed: bool = Field(
        description="Is the GeoPolylinie closed (polygon)?",
        frontend_type="boolean",
        frontend_readonly=False,
        frontend_required=True,
    )
    punkte: List[GeoPunkt] = Field(
        default_factory=list,
        description="List of GeoPunkte forming the GeoPolylinie",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=True,
    )


class Dokument(BaseModel):
    """Supporting data object for file and URL management with versioning."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    mandateId: str = Field(
        description="ID of the mandate this document belongs to",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    featureInstanceId: str = Field(
        description="ID of the feature instance this document belongs to",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    label: str = Field(
        description="Document label",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    versionsbezeichnung: Optional[str] = Field(
        None,
        description="Version number or designation (e.g. 'v1.0', 'Rev. A')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumentTyp: Optional[DokumentTyp] = Field(
        None,
        description="Document type",
        frontend_type="select",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumentReferenz: str = Field(
        description="File path or URL",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    quelle: Optional[str] = Field(
        None,
        description="Source of the document",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    mimeType: Optional[str] = Field(
        None,
        description="MIME type of the document (e.g. 'application/pdf', 'image/png')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    kategorienTags: List[str] = Field(
        default_factory=list,
        description="Document categorization tags",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )


class Kontext(BaseModel):
    """Supporting data object for flexible additional information."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
    )
    thema: str = Field(
        description="Theme designation",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    inhalt: str = Field(
        description="Detailed information (text)",
        frontend_type="textarea",
        frontend_readonly=False,
        frontend_required=True,
    )


class Land(BaseModel):
    """National level administrative entity."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    mandateId: str = Field(
        description="ID of the mandate",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    featureInstanceId: str = Field(
        description="ID of the feature instance",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    label: str = Field(
        description="Country name (e.g. 'Schweiz')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    abk: Optional[str] = Field(
        None,
        description="Abbreviation (e.g. 'CH')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumente: List[Dokument] = Field(
        default_factory=list,
        description="National laws/documents",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    kontextInformationen: List[Kontext] = Field(
        default_factory=list,
        description="National context information",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )


class Kanton(BaseModel):
    """Cantonal level administrative entity."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    mandateId: str = Field(
        description="ID of the mandate",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    featureInstanceId: str = Field(
        description="ID of the feature instance",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    label: str = Field(
        description="Canton name (e.g. 'Zürich')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    id_land: Optional[str] = Field(
        None,
        description="Land ID (Foreign Key) - eindeutiger Link zum Land, in welchem Land der Kanton liegt",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    abk: Optional[str] = Field(
        None,
        description="Abbreviation (e.g. 'ZH')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumente: List[Dokument] = Field(
        default_factory=list,
        description="Cantonal documents",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    kontextInformationen: List[Kontext] = Field(
        default_factory=list,
        description="Canton-specific context information",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )


class Gemeinde(BaseModel):
    """Municipal level administrative entity."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    mandateId: str = Field(
        description="ID of the mandate",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    featureInstanceId: str = Field(
        description="ID of the feature instance",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    label: str = Field(
        description="Municipality name (e.g. 'Zürich')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    id_kanton: Optional[str] = Field(
        None,
        description="Kanton ID (Foreign Key) - eindeutiger Link zum Kanton, in welchem Kanton die Gemeinde liegt",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    plz: Optional[str] = Field(
        None,
        description="Postal code (for municipalities with multiple PLZ, this can be a main PLZ). Bei Gemeinden mit mehreren Postleitzahlen wird die konkrete PLZ der Parzelle im Attribut `plz` der Parzelle erfasst.",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumente: List[Dokument] = Field(
        default_factory=list,
        description="Municipal documents",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    kontextInformationen: List[Kontext] = Field(
        default_factory=list,
        description="Municipality-specific context information",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )


# ===== Main Models (use ForwardRef for circular references) =====

# Forward references for circular dependencies
ParzelleRef = ForwardRef('Parzelle')


class Parzelle(BaseModel):
    """Represents a plot with all building law properties."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    mandateId: str = Field(
        description="ID of the mandate",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    featureInstanceId: str = Field(
        description="ID of the feature instance",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    
    # Grunddaten
    label: str = Field(
        description="Plot designation",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    parzellenAliasTags: List[str] = Field(
        default_factory=list,
        description="Additional plot names or field names",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    eigentuemerschaft: Optional[str] = Field(
        None,
        description="Owner of the plot",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    strasseNr: Optional[str] = Field(
        None,
        description="Street and house number",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    plz: Optional[str] = Field(
        None,
        description="Postal code of the plot",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    # Geografischer Kontext
    perimeter: Optional[GeoPolylinie] = Field(
        None,
        description="Plot boundary as closed GeoPolylinie",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    baulinie: Optional[GeoPolylinie] = Field(
        None,
        description="Building line of the plot",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    kontextGemeinde: Optional[str] = Field(
        None,
        description="Municipality ID (Foreign Key)",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    # Bebauungsparameter
    bauzone: Optional[str] = Field(
        None,
        description="Building zone designation (e.g. W3, WG2, etc.)",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    az: Optional[float] = Field(
        None,
        description="Ausnützungsziffer",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    bz: Optional[float] = Field(
        None,
        description="Bebauungsziffer",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    vollgeschossZahl: Optional[int] = Field(
        None,
        description="Number of allowed full floors",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    anrechenbarDachgeschoss: Optional[float] = Field(
        None,
        description="Accountable portion of attic (0.0 - 1.0)",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    anrechenbarUntergeschoss: Optional[float] = Field(
        None,
        description="Accountable portion of basement (0.0 - 1.0)",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    gebaeudehoeheMax: Optional[float] = Field(
        None,
        description="Maximum building height in meters",
        frontend_type="number",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    # Abstandsregelungen
    regelnGrenzabstand: List[str] = Field(
        default_factory=list,
        description="Regulations for boundary distance",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    regelnMehrlaengenzuschlag: List[str] = Field(
        default_factory=list,
        description="Regulations for additional length surcharge",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    regelnMehrhoehenzuschlag: List[str] = Field(
        default_factory=list,
        description="Regulations for additional height surcharge",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    # Eigenschaften (Ja/Nein)
    parzelleBebaut: Optional[JaNein] = Field(
        None,
        description="Is the plot built?",
        frontend_type="select",
        frontend_readonly=False,
        frontend_required=False,
    )
    parzelleErschlossen: Optional[JaNein] = Field(
        None,
        description="Is the plot developed?",
        frontend_type="select",
        frontend_readonly=False,
        frontend_required=False,
    )
    parzelleHanglage: Optional[JaNein] = Field(
        None,
        description="Is the plot on a slope?",
        frontend_type="select",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    # Schutzzonen
    laermschutzzone: Optional[str] = Field(
        None,
        description="Noise protection zone (e.g. 'II')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    hochwasserschutzzone: Optional[str] = Field(
        None,
        description="Flood protection zone (e.g. 'tief')",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    grundwasserschutzzone: Optional[str] = Field(
        None,
        description="Groundwater protection zone",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=False,
    )
    
    # Beziehungen (stored as JSONB in database)
    parzellenNachbarschaft: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Neighboring plots (stored as list of Parzelle IDs or full objects)",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumente: List[Dokument] = Field(
        default_factory=list,
        description="Plot-specific documents",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    kontextInformationen: List[Kontext] = Field(
        default_factory=list,
        description="Plot-specific context information",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )


class Projekt(BaseModel):
    """Core object representing a construction project."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    mandateId: str = Field(
        description="ID of the mandate",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    featureInstanceId: str = Field(
        description="ID of the feature instance",
        frontend_type="text",
        frontend_readonly=True,
        frontend_required=False,
    )
    label: str = Field(
        description="Project designation",
        frontend_type="text",
        frontend_readonly=False,
        frontend_required=True,
    )
    statusProzess: Optional[StatusProzess] = Field(
        None,
        description="Project status",
        frontend_type="select",
        frontend_readonly=False,
        frontend_required=False,
    )
    perimeter: Optional[GeoPolylinie] = Field(
        None,
        description="Envelope of all plots in the project",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    baulinie: Optional[GeoPolylinie] = Field(
        None,
        description="Building line of the project",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    parzellen: List[Parzelle] = Field(
        default_factory=list,
        description="All plots of the project",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    dokumente: List[Dokument] = Field(
        default_factory=list,
        description="Project-specific documents",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )
    kontextInformationen: List[Kontext] = Field(
        default_factory=list,
        description="Project-specific context information",
        frontend_type="json",
        frontend_readonly=False,
        frontend_required=False,
    )


# Resolve forward references
Parzelle.model_rebuild()
Projekt.model_rebuild()


# Register labels for frontend
registerModelLabels(
    "Projekt",
    {"en": "Project", "fr": "Projet", "de": "Projekt"},
    {
        "id": {"en": "ID", "fr": "ID", "de": "ID"},
        "label": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
        "statusProzess": {"en": "Process Status", "fr": "Statut du processus", "de": "Prozessstatus"},
        "mandateId": {"en": "Mandate ID", "fr": "ID du mandat", "de": "Mandats-ID"},
        "featureInstanceId": {"en": "Feature Instance ID", "fr": "ID de l'instance", "de": "Feature-Instanz-ID"},
    },
)

registerModelLabels(
    "Parzelle",
    {"en": "Plot", "fr": "Parcelle", "de": "Parzelle"},
    {
        "id": {"en": "ID", "fr": "ID", "de": "ID"},
        "label": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
        "mandateId": {"en": "Mandate ID", "fr": "ID du mandat", "de": "Mandats-ID"},
        "featureInstanceId": {"en": "Feature Instance ID", "fr": "ID de l'instance", "de": "Feature-Instanz-ID"},
    },
)

registerModelLabels(
    "Dokument",
    {"en": "Document", "fr": "Document", "de": "Dokument"},
    {
        "id": {"en": "ID", "fr": "ID", "de": "ID"},
        "label": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
        "mandateId": {"en": "Mandate ID", "fr": "ID du mandat", "de": "Mandats-ID"},
        "featureInstanceId": {"en": "Feature Instance ID", "fr": "ID de l'instance", "de": "Feature-Instanz-ID"},
    },
)

