"""
Ontology Models - Data models for ontology artifacts.

Defines the core data structures for field-level semantic annotations,
concept tiers, node kinds, relationships, lineage, and inverse mappings.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any


class ConceptTier(str, Enum):
    """Governance tier for concepts."""

    CORE = "core"
    """Tier 1: Core concepts governed by central team."""

    DOMAIN = "domain"
    """Tier 2: Domain concepts governed by domain teams."""

    FREE = "free"
    """Tier 3: Free-form concepts with no governance."""


class NodeKind(str, Enum):
    """Classification of concept nodes in the knowledge graph."""

    ENTITY = "entity"
    """A thing with identity (customer, product, system)."""

    PROCESS = "process"
    """A sequence of steps (checkout, onboarding)."""

    EVENT = "event"
    """Something that happens at a point in time."""

    ATTRIBUTE = "attribute"
    """A property or measurement."""

    RULE = "rule"
    """A constraint or policy."""

    SYSTEM = "system"
    """An external system or service."""

    CONCEPT = "concept"
    """An abstract idea."""

    METRIC = "metric"
    """A computed business measure."""


class RelationshipType(str, Enum):
    """Types of relationships between fields or concepts."""

    # Original field-level types (backward compatible)
    REFERENCES = "references"
    """Foreign key or reference relationship."""

    DERIVED_FROM = "derived_from"
    """Field is computed from another field."""

    SAME_AS = "same_as"
    """Fields represent the same concept."""

    RELATED_TO = "related_to"
    """General relationship."""

    # Knowledge schema types
    IS_A = "is_a"
    """X is a kind of Y (taxonomy)."""

    HAS = "has"
    """X contains or owns Y (composition)."""

    DEPENDS_ON = "depends_on"
    """X requires Y to function."""

    PRECEDES = "precedes"
    """X happens before Y (temporal ordering)."""

    CAUSES = "causes"
    """X leads to or triggers Y."""

    IMPLEMENTS = "implements"
    """X realizes or is a concrete form of Y."""

    GOVERNS = "governs"
    """X constrains or regulates Y."""

    DESCRIBES = "describes"
    """X is a property or attribute of Y."""

    EQUIVALENT_TO = "equivalent_to"
    """X represents the same thing as Y."""


INVERSE_MAP: dict[str, str] = {
    "is_a": "has_subtype",
    "has": "part_of",
    "depends_on": "depended_on_by",
    "derived_from": "derives",
    "precedes": "follows",
    "causes": "caused_by",
    "implements": "implemented_by",
    "governs": "governed_by",
    "describes": "described_by",
    "equivalent_to": "equivalent_to",
    "related_to": "related_to",
    "references": "referenced_by",
    "same_as": "same_as",
}
"""Mapping from each relationship type to its inverse label."""


def resolve_inverse(relationship_type: str) -> str:
    """Return the inverse name for a relationship type."""
    return INVERSE_MAP.get(relationship_type, relationship_type)


@dataclass
class Lineage:
    """
    Tracks the origin of a field.

    Attributes:
        derived_from: Field this was derived from (if any)
        source_system: System that produces this field
    """

    derived_from: str | None = None
    source_system: str | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "derived_from": self.derived_from,
            "source_system": self.source_system,
        }


@dataclass
class Relationship:
    """
    A relationship between two fields.

    Attributes:
        target: Target field path (e.g., "customers.id")
        type: Type of relationship
    """

    target: str
    type: RelationshipType

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "target": self.target,
            "type": self.type.value,
        }


@dataclass
class ConceptDefinition:
    """
    Full definition of a single concept in the vocabulary.

    Mirrors the extended ontologies.yaml schema and can be losslessly
    converted to/from SKOS JSON-LD.
    """

    id: str
    label: str
    definition: str | None = None
    broader: str | None = None
    tags: list[str] = field(default_factory=list)
    alt_labels: list[str] = field(default_factory=list)
    notation: str | None = None
    examples: list[str] = field(default_factory=list)
    exact_match: str | None = None
    deprecated: bool = False
    replaced_by: str | None = None
    node_kind: str | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary (round-trip safe with YAML schema)."""
        result: dict[str, Any] = {"id": self.id, "label": self.label}
        if self.definition is not None:
            result["definition"] = self.definition
        if self.broader is not None:
            result["broader"] = self.broader
        if self.tags:
            result["tags"] = self.tags
        if self.alt_labels:
            result["alt_labels"] = self.alt_labels
        if self.notation is not None:
            result["notation"] = self.notation
        if self.examples:
            result["examples"] = self.examples
        if self.exact_match is not None:
            result["exact_match"] = self.exact_match
        if self.deprecated:
            result["deprecated"] = self.deprecated
        if self.replaced_by is not None:
            result["replaced_by"] = self.replaced_by
        if self.node_kind is not None:
            result["node_kind"] = self.node_kind
        return result


@dataclass
class ConceptScheme:
    """
    A complete concept scheme (vocabulary).

    Mirrors the top-level ``scheme:`` + ``concepts:`` structure in
    ontologies.yaml.
    """

    id: str
    title: str
    version: str
    description: str = ""
    base_uri: str | None = None
    concepts: list[ConceptDefinition] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        result: dict[str, Any] = {
            "scheme": {
                "id": self.id,
                "title": self.title,
                "version": self.version,
            },
            "concepts": [c.to_dict() for c in self.concepts],
        }
        if self.description:
            result["scheme"]["description"] = self.description
        if self.base_uri:
            result["scheme"]["base_uri"] = self.base_uri
        return result


@dataclass
class FieldOntology:
    """
    Semantic annotation for a single field.

    Attributes:
        concept: The semantic concept this field represents
        broader: Parent concept in the hierarchy (deprecated -- use ontologies.yaml)
        definition: Human-readable definition (deprecated -- use ontologies.yaml)
        tags: Tags for categorization
        owner: Team or person owning this field
        description: Field-specific business description
        deprecated: Whether this field mapping is deprecated
        lineage: Data lineage information
        relationships: Relationships to other fields
    """

    concept: str
    broader: str | None = None
    definition: str | None = None
    tags: list[str] = field(default_factory=list)
    owner: str | None = None
    description: str | None = None
    deprecated: bool = False
    lineage: Lineage | None = None
    relationships: list[Relationship] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        result: dict[str, Any] = {
            "concept": self.concept,
        }
        if self.broader is not None:
            result["broader"] = self.broader
        if self.definition is not None:
            result["definition"] = self.definition
        if self.tags:
            result["tags"] = self.tags
        if self.owner is not None:
            result["owner"] = self.owner
        if self.description is not None:
            result["description"] = self.description
        if self.deprecated:
            result["deprecated"] = self.deprecated
        if self.lineage is not None:
            result["lineage"] = self.lineage.to_dict()
        if self.relationships:
            result["relationships"] = [r.to_dict() for r in self.relationships]
        return result


@dataclass
class OntologyArtifact:
    """
    A complete ontology artifact for a data contract.

    Attributes:
        version: Ontology version string
        fields: Mapping of field name to its semantic annotation
    """

    version: str
    fields: dict[str, FieldOntology] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "version": self.version,
            "fields": {name: fo.to_dict() for name, fo in self.fields.items()},
        }
