"""Internal data helpers for the Resource Registry Service.

Contains the built-in type definitions and DB ↔ domain conversion
functions.  Extracted from ``resource_registry_service.py`` to keep
individual modules under the 500-line CONTRIBUTING limit.

.. note::

    This is an **internal** module (``_``-prefixed).  External code
    should import from ``resource_registry_service`` instead.
"""

from __future__ import annotations

import json
from collections import deque
from datetime import UTC, datetime
from typing import Any

from cleveragents.application.services._resource_registry_cloud import (
    AWS_TYPES,
    CLOUD_BASE_TYPES,
    GCP_AZURE_TYPES,
)
from cleveragents.application.services._resource_registry_container import (
    CONTAINER_INFRA_TYPES,
)
from cleveragents.application.services._resource_registry_lsp import (
    LSP_RESOURCE_TYPES,
)
from cleveragents.application.services._resource_registry_physical import (
    DEFERRED_PHYSICAL_TYPES,
)
from cleveragents.application.services._resource_registry_virtual import (
    VIRTUAL_CORE_TYPES,
)
from cleveragents.application.services._resource_registry_virtual_deferred import (
    VIRTUAL_DEFERRED_TYPES,
)
from cleveragents.domain.models.core.resource import PhysVirt, Resource
from cleveragents.domain.models.core.resource_type import (
    ResourceKind,
    ResourceTypeSpec,
    SandboxStrategy,
)
from cleveragents.infrastructure.database.models import (
    ResourceLinkModel,
    ResourceModel,
    ResourceTypeModel,
)
from cleveragents.resource.handlers.database import DATABASE_TYPE_DEFS

__all__ = [
    "BUILTIN_TYPES",
    "db_resource_to_domain",
    "db_to_spec",
    "get_ancestors",
    "spec_to_db",
]

# ---------------------------------------------------------------------------
# Shared constants
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# Built-in resource type definitions (registered at startup)
# ---------------------------------------------------------------------------

# IMPORTANT: Order matters — children must appear after their parents so
# that ``bootstrap_builtin_types()`` can resolve ``inherits`` references.

# ===== Git / Filesystem / Container types (unchanged) =====

_GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
    {
        "name": "fs-mount",
        "description": "A physical mount point on the local system.",
        "resource_kind": "physical",
        "sandbox_strategy": "copy_on_write",
        "user_addable": True,
        "built_in": True,
        "cli_args": [
            {
                "name": "path",
                "type": "path",
                "required": True,
                "description": "Path to the mount point.",
            },
        ],
        "parent_types": [],
        "child_types": ["fs-directory"],
        "auto_discovery": {
            "enabled": True,
            "scan_depth": 1,
            "rules": [
                {"type": "fs-directory", "pattern": "*/"},
            ],
        },
        "handler": "cleveragents.resource.handlers.fs_mount:FsMountHandler",
        "capabilities": {
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": False,
        },
    },
    {
        "name": "git-checkout",
        "description": "A local git checkout (cloned repository or worktree).",
        "resource_kind": "physical",
        "sandbox_strategy": "git_worktree",
        "user_addable": True,
        "built_in": True,
        "cli_args": [
            {
                "name": "path",
                "type": "path",
                "required": True,
                "description": "Path to the git checkout directory.",
            },
            {
                "name": "branch",
                "type": "string",
                "required": False,
                "description": "Branch to use (defaults to current HEAD).",
                "default": None,
            },
        ],
        "parent_types": [],
        "child_types": [
            "git",
            "fs-directory",
            "fs-file",
            "devcontainer-instance",
            "devcontainer-file",
        ],
        "handler": "cleveragents.resource.handlers.git_checkout:GitCheckoutHandler",
        "capabilities": {
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": True,
        },
    },
    {
        "name": "fs-directory",
        "description": "A local filesystem directory.",
        "resource_kind": "physical",
        "sandbox_strategy": "copy_on_write",
        "user_addable": True,
        "built_in": True,
        "cli_args": [
            {
                "name": "path",
                "type": "path",
                "required": True,
                "description": "Path to the directory.",
            },
        ],
        "parent_types": ["git-checkout", "fs-mount", "fs-directory"],
        "child_types": [
            "fs-directory",
            "fs-file",
            "fs-symlink",
            "fs-hardlink",
            "devcontainer-instance",
            "devcontainer-file",
        ],
        "auto_discovery": {
            "enabled": True,
            "scan_depth": 1,
            "rules": [
                {"type": "fs-directory", "pattern": "*/"},
                {"type": "fs-file", "pattern": "*"},
                {"type": "fs-symlink", "pattern": "symlink:*"},
                {"type": "fs-hardlink", "pattern": "hardlink:*"},
            ],
        },
        "handler": "cleveragents.resource.handlers.fs_directory:FsDirectoryHandler",
        "capabilities": {
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": False,
        },
    },
    {
        "name": "container-instance",
        "description": "A container execution environment instance.",
        "resource_kind": "physical",
        "sandbox_strategy": "snapshot",
        "user_addable": True,
        "built_in": True,
        "cli_args": [
            {
                "name": "image",
                "type": "string",
                "required": False,
                "description": (
                    "Container image reference. Required when "
                    "--container-id is not provided."
                ),
                "default": None,
            },
            {
                "name": "container_id",
                "type": "string",
                "required": False,
                "description": (
                    "ID of an existing container to attach (alternative to --image)."
                ),
                "default": None,
            },
            {
                "name": "mounts",
                "type": "string",
                "required": False,
                "description": (
                    "Mount specifications as JSON array. Each entry: "
                    "{source, target, mode, type}. Formats: "
                    "<resource>:<path> or <host-path>:<path>:<mode>. "
                    "Use --mount flag (repeatable) on the CLI."
                ),
                "default": None,
            },
        ],
        "parent_types": [
            "container-runtime",
            "container-image",
            "git-checkout",
            "fs-directory",
        ],
        "child_types": [
            "container-mount",
            "container-exec-env",
            "container-port",
        ],
        "handler": ("cleveragents.resource.handlers.devcontainer:DevcontainerHandler"),
        "capabilities": {
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": True,
        },
    },
    {
        "name": "devcontainer-instance",
        "description": (
            "A devcontainer execution environment auto-discovered "
            "from .devcontainer/ configuration."
        ),
        "resource_kind": "physical",
        "sandbox_strategy": "snapshot",
        "user_addable": True,
        "built_in": True,
        "inherits": "container-instance",
        "cli_args": [
            {
                "name": "path",
                "type": "path",
                "required": True,
                "description": (
                    "Path to the directory containing .devcontainer/devcontainer.json."
                ),
            },
        ],
        "parent_types": ["git-checkout", "fs-directory"],
        "child_types": ["devcontainer-file"],
        "auto_discovery": {
            "trigger_types": ["git-checkout", "fs-directory"],
            "scan_paths": [
                ".devcontainer/devcontainer.json",
                ".devcontainer.json",
            ],
        },
        "handler": ("cleveragents.resource.handlers.devcontainer:DevcontainerHandler"),
        "capabilities": {
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": False,
        },
    },
    {
        "name": "devcontainer-file",
        "description": "A single devcontainer.json configuration file resource.",
        "resource_kind": "physical",
        "sandbox_strategy": "copy_on_write",
        "user_addable": False,
        "built_in": True,
        "cli_args": [],
        "parent_types": ["devcontainer-instance"],
        "child_types": [],
        "handler": ("cleveragents.resource.handlers.devcontainer:DevcontainerHandler"),
        "capabilities": {
            "read": True,
            "write": False,
            "sandbox": False,
            "checkpoint": False,
        },
    },
]


# ===== Generic cloud base types (provider-agnostic) =====
#
# Abstract base types that define common cloud concepts.  Provider-
# specific types (``aws-*``, ``azure-*``, etc.) inherit from these.
# Not user-addable — users create provider-specific account types.


# ===== Assemble final list =====

BUILTIN_TYPES: list[dict[str, Any]] = [
    *_GIT_FS_CONTAINER_TYPES,
    # Deferred physical resource types -- see _resource_registry_physical.py (#330)
    *DEFERRED_PHYSICAL_TYPES,
    # Cloud resource types -- see _resource_registry_cloud.py
    *CLOUD_BASE_TYPES,
    *AWS_TYPES,
    *GCP_AZURE_TYPES,
    # Container infrastructure types -- see _resource_registry_container.py (#831)
    *CONTAINER_INFRA_TYPES,
    *DATABASE_TYPE_DEFS,
    # Virtual core resource types -- see _resource_registry_virtual.py (#329)
    *VIRTUAL_CORE_TYPES,
    # Deferred virtual types -- _resource_registry_virtual_deferred (#331)
    *VIRTUAL_DEFERRED_TYPES,
    # LSP resource types -- _resource_registry_lsp (#832)
    *LSP_RESOURCE_TYPES,
]


# ---------------------------------------------------------------------------
# DB ↔ domain conversion helpers
# ---------------------------------------------------------------------------


def spec_to_db(spec: ResourceTypeSpec, source: str) -> ResourceTypeModel:
    """Convert a ResourceTypeSpec domain object to a DB model."""
    now_iso = datetime.now(tz=UTC).isoformat()

    # Determine namespace: built-in types get "builtin", custom types
    # use their namespace prefix
    namespace = "builtin"
    if "/" in spec.name:
        parts = spec.name.split("/", 1)
        namespace = parts[0]

    args_schema_json: str | None = None
    if spec.cli_args:
        args_list = [
            {
                "name": arg.name,
                "type": arg.type,
                "required": arg.required,
                "description": arg.description,
                "default": arg.default,
                "validation_pattern": arg.validation_pattern,
            }
            for arg in spec.cli_args
        ]
        args_schema_json = json.dumps(args_list)

    allowed_parent_json: str | None = None
    if spec.parent_types:
        allowed_parent_json = json.dumps(spec.parent_types)

    allowed_child_json: str | None = None
    if spec.child_types:
        allowed_child_json = json.dumps(spec.child_types)

    auto_discover_json: str | None = None
    if spec.auto_discovery is not None:
        auto_discover_json = json.dumps(spec.auto_discovery)

    capabilities_json: str | None = None
    if spec.capabilities:
        capabilities_json = json.dumps(spec.capabilities)

    equivalence_json: str | None = None
    if spec.equivalence is not None:
        equivalence_json = json.dumps(spec.equivalence)

    return ResourceTypeModel(
        name=spec.name,
        namespace=namespace,
        description=spec.description or None,
        resource_kind=spec.resource_kind.value
        if isinstance(spec.resource_kind, ResourceKind)
        else spec.resource_kind,
        sandbox_strategy=spec.sandbox_strategy.value
        if isinstance(spec.sandbox_strategy, SandboxStrategy)
        else spec.sandbox_strategy,
        user_addable=spec.user_addable,
        handler_ref=spec.handler,
        args_schema_json=args_schema_json,
        allowed_parent_types_json=allowed_parent_json,
        allowed_child_types_json=allowed_child_json,
        auto_discover_json=auto_discover_json,
        capabilities_json=capabilities_json,
        equivalence_json=equivalence_json,
        inherits=spec.inherits,
        source=source,
        created_at=now_iso,
        updated_at=now_iso,
    )


def db_to_spec(row: ResourceTypeModel) -> ResourceTypeSpec:
    """Convert a ResourceTypeModel DB row to a ResourceTypeSpec domain object."""
    cli_args_data: list[dict[str, Any]] = []
    raw_args = getattr(row, "args_schema_json", None)
    if raw_args is not None:
        cli_args_data = json.loads(str(raw_args))

    parent_types: list[str] = []
    raw_parents = getattr(row, "allowed_parent_types_json", None)
    if raw_parents is not None:
        parent_types = json.loads(str(raw_parents))

    child_types: list[str] = []
    raw_children = getattr(row, "allowed_child_types_json", None)
    if raw_children is not None:
        child_types = json.loads(str(raw_children))

    auto_discovery: dict[str, Any] | None = None
    raw_auto = getattr(row, "auto_discover_json", None)
    if raw_auto is not None:
        auto_discovery = json.loads(str(raw_auto))

    capabilities: dict[str, bool] = {
        "read": True,
        "write": True,
        "sandbox": True,
        "checkpoint": False,
    }
    raw_caps = getattr(row, "capabilities_json", None)
    if raw_caps is not None:
        capabilities = json.loads(str(raw_caps))

    equivalence: dict[str, Any] | None = None
    raw_equiv = getattr(row, "equivalence_json", None)
    if raw_equiv is not None:
        equivalence = json.loads(str(raw_equiv))

    name_str = str(row.name)
    # Derive built_in from the namespace column: built-in types have
    # namespace="builtin".  Fall back to the name heuristic only when the
    # column is unavailable (e.g. legacy rows without a namespace column).
    raw_ns = getattr(row, "namespace", None)
    is_builtin = str(raw_ns) == "builtin" if raw_ns is not None else "/" not in name_str

    raw_desc = getattr(row, "description", None)
    desc_str = str(raw_desc) if raw_desc is not None else ""
    raw_handler = getattr(row, "handler_ref", None)
    handler_str: str | None = str(raw_handler) if raw_handler is not None else None

    raw_inherits = getattr(row, "inherits", None)
    inherits_str: str | None = str(raw_inherits) if raw_inherits is not None else None

    return ResourceTypeSpec.from_config(
        {
            "name": name_str,
            "description": desc_str,
            "resource_kind": str(row.resource_kind),
            "sandbox_strategy": str(row.sandbox_strategy),
            "user_addable": bool(row.user_addable),
            "cli_args": cli_args_data,
            "parent_types": parent_types,
            "child_types": child_types,
            "auto_discovery": auto_discovery,
            "equivalence": equivalence,
            "handler": handler_str,
            "capabilities": capabilities,
            "built_in": is_builtin,
            "inherits": inherits_str,
        }
    )


def db_resource_to_domain(row: ResourceModel) -> Resource:
    """Convert a ResourceModel DB row to a Resource domain object."""
    props: dict[str, str | int | float | bool | None] = {}
    raw_props = getattr(row, "properties_json", None)
    if raw_props is not None:
        props = json.loads(str(raw_props))

    raw_ns_name = getattr(row, "namespaced_name", None)
    ns_name: str | None = str(raw_ns_name) if raw_ns_name is not None else None
    raw_desc = getattr(row, "description", None)
    desc: str | None = str(raw_desc) if raw_desc is not None else None
    raw_loc = getattr(row, "location", None)
    loc: str | None = str(raw_loc) if raw_loc is not None else None
    raw_hash = getattr(row, "content_hash", None)
    c_hash: str | None = str(raw_hash) if raw_hash is not None else None

    return Resource(
        resource_id=str(row.resource_id),
        name=ns_name,
        resource_type_name=str(row.type_name),
        classification=PhysVirt(str(row.resource_kind)),
        description=desc,
        properties=props,
        location=loc,
        content_hash=c_hash,
        sandbox_strategy=None,
        created_at=datetime.fromisoformat(str(row.created_at)),
        updated_at=datetime.fromisoformat(str(row.updated_at)),
    )


def get_ancestors(session: Any, resource_id: str) -> set[str]:
    """Walk parent links to collect all ancestor resource IDs.

    Uses BFS to traverse the DAG upward from ``resource_id``.
    """
    ancestors: set[str] = set()
    queue: deque[str] = deque([resource_id])
    while queue:
        current = queue.popleft()
        parent_links = (
            session.query(ResourceLinkModel).filter_by(child_id=current).all()
        )
        for link in parent_links:
            pid = str(link.parent_id)
            if pid not in ancestors:
                ancestors.add(pid)
                queue.append(pid)
    return ancestors
