"""UKO Layer 2 paradigm vocabulary definitions.

Defines the OWL class and property vocabularies for the three Layer 2
paradigm specializations: Object-Oriented (``uko-oo:``), Functional
(``uko-func:``), and Procedural (``uko-proc:``).

Each vocabulary declares:
- Classes with their ``rdfs:subClassOf`` parent URIs
- Object properties with domain/range/subPropertyOf
- Namespace prefix and IRI

Based on ``docs/specification.md`` ~lines 42384-42468.
"""

from __future__ import annotations

import functools

import structlog
from pydantic import BaseModel, ConfigDict, Field, field_validator

__all__: list[str] = [
    "UKO_ATOM",
    "UKO_BOUNDARY",
    "UKO_CODE_CALLABLE",
    "UKO_CODE_TYPE_DEFINITION",
    "UKO_CONTAINER",
    "UKO_DEPENDS_ON",
    "UKO_FUNC_IRI",
    "UKO_FUNC_PREFIX",
    "UKO_OO_IRI",
    "UKO_OO_PREFIX",
    "UKO_PROC_IRI",
    "UKO_PROC_PREFIX",
    "ParadigmVocabulary",
    "VocabularyClass",
    "VocabularyProperty",
    "get_func_vocabulary",
    "get_oo_vocabulary",
    "get_proc_vocabulary",
]

_log = structlog.get_logger(__name__)

# ---------------------------------------------------------------------------
# Namespace constants (spec line 41900-41902)
# ---------------------------------------------------------------------------

UKO_OO_PREFIX = "uko-oo:"
UKO_OO_IRI = "https://cleveragents.ai/ontology/uko/oo#"

UKO_FUNC_PREFIX = "uko-func:"
UKO_FUNC_IRI = "https://cleveragents.ai/ontology/uko/func#"

UKO_PROC_PREFIX = "uko-proc:"
UKO_PROC_IRI = "https://cleveragents.ai/ontology/uko/proc#"

# Layer 1 URIs that Layer 2 depends on (will be wired when Layer 1 lands)
UKO_CODE_TYPE_DEFINITION = "https://cleveragents.ai/ontology/uko/code#TypeDefinition"
UKO_CODE_CALLABLE = "https://cleveragents.ai/ontology/uko/code#Callable"

# Layer 0 URIs
UKO_CONTAINER = "https://cleveragents.ai/ontology/uko#Container"
UKO_ATOM = "https://cleveragents.ai/ontology/uko#Atom"
UKO_BOUNDARY = "https://cleveragents.ai/ontology/uko#Boundary"
UKO_DEPENDS_ON = "https://cleveragents.ai/ontology/uko#dependsOn"


# ---------------------------------------------------------------------------
# Shared URI validation helper
# ---------------------------------------------------------------------------


def _validate_http_uri(uri: str, *, field_name: str = "URI") -> str:
    """Validate that *uri* uses an HTTP or HTTPS scheme.

    This is a **scheme-only** check.  These URIs are used as ontology
    identifiers and are never fetched at runtime, so full URL parsing
    and SSRF mitigation are not required here.

    Args:
        uri: The URI string to validate.
        field_name: Human-readable field name for error messages.

    Returns:
        The validated URI string (unchanged).

    Raises:
        ValueError: If *uri* does not start with ``http://`` or ``https://``.
    """
    if not uri.startswith(("http://", "https://")):
        raise ValueError(f"{field_name} must use http(s) scheme, got: {uri!r}")
    return uri


# ---------------------------------------------------------------------------
# Vocabulary models
# ---------------------------------------------------------------------------


class VocabularyClass(BaseModel, frozen=True):
    """An OWL class definition within a paradigm vocabulary.

    Represents a single ``owl:Class`` with its label, comment, and
    ``rdfs:subClassOf`` parent URIs.

    Args:
        uri: Full IRI of this class.
        label: Human-readable label (``rdfs:label``).
        comment: Description (``rdfs:comment``).
        parent_uris: Tuple of ``rdfs:subClassOf`` parent IRIs.
    """

    uri: str = Field(
        ...,
        min_length=1,
        description="Full IRI of this OWL class",
    )
    label: str = Field(
        ...,
        min_length=1,
        description="rdfs:label for this class",
    )
    comment: str = Field(
        default="",
        description="rdfs:comment describing this class",
    )
    parent_uris: tuple[str, ...] = Field(
        default=(),
        description="rdfs:subClassOf parent IRIs",
    )

    @field_validator("uri")
    @classmethod
    def _validate_uri(cls: type[VocabularyClass], v: str) -> str:
        """Validate URI uses an HTTP(S) scheme."""
        return _validate_http_uri(v, field_name="class URI")

    @field_validator("parent_uris")
    @classmethod
    def _validate_parent_uris(
        cls: type[VocabularyClass],
        v: tuple[str, ...],
    ) -> tuple[str, ...]:
        """Validate all parent URIs use an HTTP(S) scheme."""
        for uri in v:
            _validate_http_uri(uri, field_name="parent_uris entry")
        return v

    model_config = ConfigDict(str_strip_whitespace=True)


class VocabularyProperty(BaseModel, frozen=True):
    """An OWL property definition within a paradigm vocabulary.

    Represents an ``owl:ObjectProperty`` with domain, range, and
    optional ``rdfs:subPropertyOf``.

    Args:
        uri: Full IRI of this property.
        label: Human-readable label.
        domain_uri: The ``rdfs:domain`` class IRI.
        range_uri: The ``rdfs:range`` class IRI.
        sub_property_of: Optional ``rdfs:subPropertyOf`` parent IRI.
    """

    uri: str = Field(
        ...,
        min_length=1,
        description="Full IRI of this OWL property",
    )
    label: str = Field(
        ...,
        min_length=1,
        description="rdfs:label for this property",
    )
    domain_uri: str = Field(
        ...,
        min_length=1,
        description="rdfs:domain class IRI",
    )
    range_uri: str = Field(
        ...,
        min_length=1,
        description="rdfs:range class IRI",
    )
    sub_property_of: str | None = Field(
        default=None,
        description="rdfs:subPropertyOf parent IRI, or None if not a sub-property",
    )

    @field_validator("uri")
    @classmethod
    def _validate_uri(cls: type[VocabularyProperty], v: str) -> str:
        """Validate URI uses an HTTP(S) scheme."""
        return _validate_http_uri(v, field_name="property URI")

    @field_validator("domain_uri")
    @classmethod
    def _validate_domain_uri(cls: type[VocabularyProperty], v: str) -> str:
        """Validate domain URI uses an HTTP(S) scheme."""
        return _validate_http_uri(v, field_name="domain_uri")

    @field_validator("range_uri")
    @classmethod
    def _validate_range_uri(cls: type[VocabularyProperty], v: str) -> str:
        """Validate range URI uses an HTTP(S) scheme."""
        return _validate_http_uri(v, field_name="range_uri")

    @field_validator("sub_property_of")
    @classmethod
    def _validate_sub_property_of(
        cls: type[VocabularyProperty],
        v: str | None,
    ) -> str | None:
        """Validate sub_property_of URI uses an HTTP(S) scheme if set."""
        if v is not None:
            _validate_http_uri(v, field_name="sub_property_of")
        return v

    model_config = ConfigDict(str_strip_whitespace=True)


class ParadigmVocabulary(BaseModel, frozen=True):
    """A complete Layer 2 paradigm vocabulary definition.

    Groups all OWL classes and properties for a single paradigm
    (e.g., Object-Oriented, Functional, Procedural) with its
    namespace prefix and IRI.

    Args:
        prefix: Short prefix (e.g., ``"uko-oo:"``).
        iri: Full namespace IRI.
        layer: Ontology layer number (always 2 for paradigm vocabs).
        parent_domain: Parent Layer 1 domain prefix.
        classes: Tuple of OWL class definitions.
        properties: Tuple of OWL property definitions.
    """

    prefix: str = Field(
        ...,
        min_length=1,
        description="Short namespace prefix (e.g., 'uko-oo:')",
    )
    iri: str = Field(
        ...,
        min_length=1,
        description="Full namespace IRI",
    )
    layer: int = Field(
        ...,
        ge=0,
        le=7,
        description="Ontology layer number (e.g., 2 for paradigm vocabs)",
    )
    parent_domain: str = Field(
        ...,
        min_length=1,
        description="Parent domain prefix (e.g., 'uko-code:' for Layer 2)",
    )
    classes: tuple[VocabularyClass, ...] = Field(
        default=(),
        description="OWL class definitions",
    )
    properties: tuple[VocabularyProperty, ...] = Field(
        default=(),
        description="OWL property definitions",
    )

    @field_validator("prefix")
    @classmethod
    def _validate_prefix(cls: type[ParadigmVocabulary], v: str) -> str:
        """Validate prefix is non-empty and ends with a colon."""
        stripped = v.strip()
        if not stripped:
            raise ValueError("prefix must be a non-empty string")
        if not stripped.endswith(":"):
            raise ValueError(f"prefix must end with ':', got: {stripped!r}")
        return stripped

    @field_validator("iri")
    @classmethod
    def _validate_iri(cls: type[ParadigmVocabulary], v: str) -> str:
        """Validate IRI uses http or https scheme."""
        return _validate_http_uri(v, field_name="vocabulary IRI")

    def get_class(self, label: str) -> VocabularyClass | None:
        """Look up a class by its label.

        Args:
            label: The ``rdfs:label`` of the class to find.

        Returns:
            The matching VocabularyClass, or None if not found.
        """
        for cls in self.classes:
            if cls.label == label:
                return cls
        _log.debug("vocabulary.class_not_found", label=label, prefix=self.prefix)
        return None

    def get_property(self, label: str) -> VocabularyProperty | None:
        """Look up a property by its label.

        Args:
            label: The ``rdfs:label`` of the property to find.

        Returns:
            The matching VocabularyProperty, or None if not found.
        """
        for prop in self.properties:
            if prop.label == label:
                return prop
        _log.debug("vocabulary.property_not_found", label=label, prefix=self.prefix)
        return None

    def class_uris(self) -> tuple[str, ...]:
        """Return all class URIs in this vocabulary."""
        return tuple(cls.uri for cls in self.classes)

    def property_uris(self) -> tuple[str, ...]:
        """Return all property URIs in this vocabulary."""
        return tuple(prop.uri for prop in self.properties)

    model_config = ConfigDict(str_strip_whitespace=True)


# ---------------------------------------------------------------------------
# uko-oo: vocabulary (spec lines 42384-42411)
# ---------------------------------------------------------------------------


@functools.lru_cache(maxsize=1)
def get_oo_vocabulary() -> ParadigmVocabulary:
    """Return the Object-Oriented paradigm vocabulary.

    Classes: Class, Interface, Method, Attribute.
    Properties: inheritsFrom, implements.

    Based on specification.md ~lines 42384-42411.
    """
    return ParadigmVocabulary(
        prefix=UKO_OO_PREFIX,
        iri=UKO_OO_IRI,
        layer=2,
        parent_domain="uko-code:",
        classes=(
            VocabularyClass(
                uri=f"{UKO_OO_IRI}Class",
                label="Class",
                comment="An OO class that contains methods and attributes.",
                parent_uris=(UKO_CODE_TYPE_DEFINITION, UKO_CONTAINER),
            ),
            VocabularyClass(
                uri=f"{UKO_OO_IRI}Interface",
                label="Interface",
                comment="An interface or abstract base class.",
                parent_uris=(UKO_CODE_TYPE_DEFINITION, UKO_BOUNDARY),
            ),
            VocabularyClass(
                uri=f"{UKO_OO_IRI}Method",
                label="Method",
                comment="A method defined within a class.",
                parent_uris=(UKO_CODE_CALLABLE,),
            ),
            VocabularyClass(
                uri=f"{UKO_OO_IRI}Attribute",
                label="Attribute",
                comment="A class or instance attribute.",
                parent_uris=(UKO_ATOM,),
            ),
        ),
        properties=(
            VocabularyProperty(
                uri=f"{UKO_OO_IRI}inheritsFrom",
                label="inheritsFrom",
                domain_uri=f"{UKO_OO_IRI}Class",
                range_uri=f"{UKO_OO_IRI}Class",
                sub_property_of=UKO_DEPENDS_ON,
            ),
            VocabularyProperty(
                uri=f"{UKO_OO_IRI}implements",
                label="implements",
                domain_uri=f"{UKO_OO_IRI}Class",
                range_uri=f"{UKO_OO_IRI}Interface",
                sub_property_of=UKO_DEPENDS_ON,
            ),
        ),
    )


# ---------------------------------------------------------------------------
# uko-func: vocabulary (spec lines 42413-42425)
# ---------------------------------------------------------------------------


@functools.lru_cache(maxsize=1)
def get_func_vocabulary() -> ParadigmVocabulary:
    """Return the Functional paradigm vocabulary.

    Classes: PureFunction, TypeClass, Monad.

    Based on specification.md ~lines 42413-42425.
    """
    return ParadigmVocabulary(
        prefix=UKO_FUNC_PREFIX,
        iri=UKO_FUNC_IRI,
        layer=2,
        parent_domain="uko-code:",
        classes=(
            VocabularyClass(
                uri=f"{UKO_FUNC_IRI}PureFunction",
                label="PureFunction",
                comment="A pure function with no side effects.",
                parent_uris=(UKO_CODE_CALLABLE,),
            ),
            VocabularyClass(
                uri=f"{UKO_FUNC_IRI}TypeClass",
                label="TypeClass",
                comment="A type class defining a set of operations for types.",
                parent_uris=(UKO_CODE_TYPE_DEFINITION, UKO_BOUNDARY),
            ),
            VocabularyClass(
                uri=f"{UKO_FUNC_IRI}Monad",
                label="Monad",
                comment="A monadic type encapsulating computation context.",
                parent_uris=(UKO_CODE_TYPE_DEFINITION,),
            ),
        ),
        properties=(),
    )


# ---------------------------------------------------------------------------
# uko-proc: vocabulary (spec lines 42460-42468)
# ---------------------------------------------------------------------------


@functools.lru_cache(maxsize=1)
def get_proc_vocabulary() -> ParadigmVocabulary:
    """Return the Procedural paradigm vocabulary.

    Classes: ProceduralFunction, GlobalVariable, HeaderFile,
    StructDefinition, Macro.

    Based on specification.md ~lines 42460-42468.
    """
    return ParadigmVocabulary(
        prefix=UKO_PROC_PREFIX,
        iri=UKO_PROC_IRI,
        layer=2,
        parent_domain="uko-code:",
        classes=(
            VocabularyClass(
                uri=f"{UKO_PROC_IRI}ProceduralFunction",
                label="ProceduralFunction",
                comment="A procedural function declaration.",
                parent_uris=(UKO_CODE_CALLABLE,),
            ),
            VocabularyClass(
                uri=f"{UKO_PROC_IRI}GlobalVariable",
                label="GlobalVariable",
                comment="A global variable declaration.",
                parent_uris=(UKO_ATOM,),
            ),
            VocabularyClass(
                uri=f"{UKO_PROC_IRI}HeaderFile",
                label="HeaderFile",
                comment="A header file defining exported declarations.",
                parent_uris=(UKO_CONTAINER,),
            ),
            VocabularyClass(
                uri=f"{UKO_PROC_IRI}StructDefinition",
                label="StructDefinition",
                comment="A C-style struct type definition.",
                parent_uris=(UKO_CODE_TYPE_DEFINITION,),
            ),
            VocabularyClass(
                uri=f"{UKO_PROC_IRI}Macro",
                label="Macro",
                comment="A preprocessor macro definition.",
                parent_uris=(UKO_ATOM,),
            ),
        ),
        properties=(),
    )
