"""
Pydantic models for data contract validation.

These models ensure that data contracts strictly adhere to the database table design.
Based on the database schema:
- data_contracts table
- schemas table
- metadata_records table
- owners table
- coercion_rules table
- validation_rules table
"""

from __future__ import annotations

import warnings
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field, field_validator

if TYPE_CHECKING:
    from typing import ForwardRef

# Suppress Pydantic warnings about field name "schema" shadowing BaseModel.schema()
# This is safe because we're using the field for data validation, not accessing BaseModel.schema()
warnings.filterwarnings(
    "ignore",
    message='.*Field name "schema".*shadows an attribute.*',
    category=UserWarning,
)


class SchemaComponent(BaseModel):
    """
    Schema component - required field.

    Must be a valid JSON Schema object. Stored in schemas table.
    """

    type: str = Field(..., description="JSON Schema type (e.g., 'object')")
    properties: dict[str, Any] = Field(None, description="JSON Schema properties")
    required: list[str] | None = Field(None, description="Required fields")
    version: str | None = Field(None, max_length=50, description="Schema version")
    title: str | None = Field(None, description="Schema title")

    model_config = {
        "extra": "allow",  # Allow additional JSON Schema fields
    }


class OwnershipComponent(BaseModel):
    """
    Ownership component - optional field.

    Stored in owners table. Matches database column constraints.
    """

    owner: str | None = Field(
        None, max_length=255, description="Owner identifier (matches owners.id)"
    )
    name: str | None = Field(
        None, max_length=255, description="Display name (matches owners.name)"
    )
    email: str | None = Field(
        None, max_length=255, description="Owner email (matches owners.email)"
    )
    team: str | None = Field(
        None, max_length=255, description="Team name (matches owners.team)"
    )
    additional_info: dict[str, Any] | None = Field(
        None, description="Additional info (stored in owners.additional_info as JSON)"
    )

    model_config = {
        "extra": "allow",  # Allow additional ownership fields
    }


class GovernanceRulesComponent(BaseModel):
    """
    Governance rules component - optional field.

    Stored in metadata_records.governance_rules as JSON.
    """

    model_config = {
        "extra": "allow",  # Governance rules can contain any structure (stored as JSON)
    }


class RuntimeComponent(BaseModel):
    """
    Runtime component - optional field.

    Groups storage_locations, serve, and environments. Stored in metadata_records
    payload under the "runtime" key. Canonical shape uses runtime; top-level
    storage_locations/serve/environments are merged into runtime by the normalizer.
    """

    storage_locations: list[Any] | None = Field(
        None,
        description="List of storage location names or {name, usage_type} for join table",
    )
    serve: dict[str, Any] | None = Field(
        None,
        description="API endpoint and params (e.g. api.type, api.endpoint, api.params)",
    )
    environments: list[Any] | None = Field(
        None,
        description="List of environment names for join table",
    )

    model_config = {
        "extra": "allow",
    }


class MetadataComponent(BaseModel):
    """
    Metadata in canonical template shape.

    Stored in metadata_records (columns + payload). ownership and lineage are
    single objects; runtime groups storage_locations, serve, environments.
    Extra keys allowed for evolution.
    """

    version: str | None = Field(None, max_length=50)
    title: str | None = Field(None, max_length=255)
    status: str | None = Field(None, max_length=50)
    type: str | None = Field(None, max_length=50)
    description: str | None = Field(None)
    created_at: Any | None = Field(None)
    updated_at: Any | None = Field(None)
    created_by: str | None = Field(None, max_length=255)
    updated_by: str | None = Field(None, max_length=255)

    ownership: OwnershipComponent | None = Field(None)
    domain: str | None = Field(None)
    lineage: dict[str, Any] | None = Field(None)
    runtime: RuntimeComponent | None = Field(None)
    lifecycle: dict[str, Any] | None = Field(None)
    serve: dict[str, Any] | None = Field(
        None,
        description="Deprecated: use runtime.serve. Kept for backward compatibility.",
    )
    data_quality: dict[str, Any] | None = Field(None)
    governance_rules: GovernanceRulesComponent | None = Field(None)
    changelog: list[Any] | None = Field(None)

    @field_validator("status")
    @classmethod
    def validate_status(cls, v: str | None) -> str | None:
        """Validate status enum values."""
        if v is not None and v not in ["active", "deprecated", "draft"]:
            raise ValueError("status must be one of: active, deprecated, draft")
        return v

    model_config = {
        "extra": "allow",  # Allow additional metadata fields (stored as JSON in database)
    }


class CoercionRulesComponent(BaseModel):
    """
    Coercion rules component - optional field.

    Stored in coercion_rules table. Structure matches database design.
    """

    version: str | None = Field(
        None, max_length=50, description="Coercion rules version"
    )
    rules: dict[str, str] | None = Field(
        None,
        description="Coercion rules mapping field names to coercion function names",
    )

    model_config = {
        "extra": "allow",  # Allow additional fields in coercion rules
    }


class ValidationRulesComponent(BaseModel):
    """
    Validation rules component - optional field.

    Stored in validation_rules table. Structure matches database design.
    """

    version: str | None = Field(
        None, max_length=50, description="Validation rules version"
    )
    rules: dict[str, dict[str, Any]] | None = Field(
        None,
        description="Validation rules mapping field names to validation configurations",
    )

    model_config = {
        "extra": "allow",  # Allow additional fields in validation rules
    }


class OntologyComponent(BaseModel):
    """
    Ontology component - semantic field annotations (optional).

    Stored in wiki tables (concepts, fields, field_relationships).
    Defines what each field means in the business domain.
    """

    version: str | None = Field(None, max_length=50, description="Ontology version")
    fields: dict[str, Any] | None = Field(
        None,
        description="Field-level ontology annotations (concept, definition, relationships)",
    )

    model_config = {
        "extra": "allow",  # Allow additional ontology fields
    }


class VersionsComponent(BaseModel):
    """
    Versions component - optional field.

    Tracks versions of all components. Stored in data_contracts table version columns.
    """

    schema: str | None = Field(  # type: ignore[assignment]
        None,
        max_length=50,
        description="Schema version",
        alias="schema",
        serialization_alias="schema",
    )
    metadata: str | None = Field(None, max_length=50, description="Metadata version")
    coercion_rules: str | None = Field(
        None, max_length=50, description="Coercion rules version"
    )
    validation_rules: str | None = Field(
        None, max_length=50, description="Validation rules version"
    )
    ontology: str | None = Field(None, max_length=50, description="Ontology version")

    model_config = {
        "extra": "forbid",  # Versions should only contain these fields
        "populate_by_name": True,
        "validate_assignment": True,
    }


class DataContractSchema(BaseModel):
    """
    Pydantic model for validating data contract structure.

    Ensures contracts strictly adhere to the database table design.
    All fields match the database schema constraints.

    Contract structure:
    - schema: JSON Schema definition (required)
    - coercion_rules: Coercion rules (optional)
    - validation_rules: Validation rules (optional)
    - metadata: Metadata containing ownership and governance_rules (optional)
    - ontology: Semantic field annotations (optional)
    - versions: Version tracking (optional)

    Note: ownership and governance_rules are nested inside metadata, not at top level.
    """

    schema: SchemaComponent = Field(  # type: ignore[assignment]
        ...,
        description="JSON Schema definition (required, stored in schemas table)",
        alias="schema",
        serialization_alias="schema",
    )
    coercion_rules: CoercionRulesComponent | None = Field(
        None, description="Coercion rules (optional, stored in coercion_rules table)"
    )
    validation_rules: ValidationRulesComponent | None = Field(
        None,
        description="Validation rules (optional, stored in validation_rules table)",
    )
    metadata: MetadataComponent | None = Field(
        None,
        description="Metadata (optional, stored in metadata_records table). Contains ownership and governance_rules.",
    )
    ontology: OntologyComponent | None = Field(
        None,
        description="Ontology annotations (optional, stored in wiki tables)",
    )
    versions: VersionsComponent | None = Field(
        None, description="Version tracking (optional, stored in data_contracts table)"
    )

    model_config = {
        "extra": "forbid",  # Only allow defined fields to ensure strict adherence to database schema
        "populate_by_name": True,
        "validate_assignment": True,
    }
