# Copyright 2026 Guildhouse Dev
# SPDX-License-Identifier: Apache-2.0

"""ConnectorRuntime — governed invocation with Chronicle emission.

Fix C-6: capability_mask enforcement per operation.
Fix C-7: AC validated against database before invocation.
Fix H-4: Chronicle INTENT emitted before execution.
"""
from __future__ import annotations

import logging
from datetime import datetime, UTC
from typing import Any, Optional

from sqlalchemy import text

from .base import ConnectorContext, ConnectorResult
from .registry import ConnectorRegistry

logger = logging.getLogger(__name__)


class ConnectorRuntime:
    def __init__(
        self, registry: ConnectorRegistry, chronicle_client: Any = None
    ) -> None:
        self.registry = registry
        self.chronicle_client = chronicle_client

    async def invoke(
        self,
        connector_id: str,
        operation: str,
        parameters: dict[str, Any],
        context: ConnectorContext,
    ) -> ConnectorResult:
        connector = self.registry.get(connector_id)
        if connector is None:
            return ConnectorResult(success=False, error=f"Unknown connector: {connector_id}")

        # Fix C-7: validate AC exists and is active
        if connector.gsap_required:
            if not context.gsap_context_id:
                return ConnectorResult(success=False, error="GSAP context required")
            ac_valid = await self._validate_ac(context.gsap_context_id)
            if not ac_valid:
                return ConnectorResult(
                    success=False,
                    error=f"AC '{context.gsap_context_id}' not found, expired, or consumed",
                )

        # Fix C-6: enforce capability_mask (only for governed connectors)
        required_cap = connector.capability_for_operation(operation) if connector.gsap_required else 0
        if required_cap and not (context.capability_mask & required_cap):
            cap_names = {0x1: "READ", 0x2: "PROPOSE", 0x4: "MUTATE", 0x8: "ADMIN"}
            return ConnectorResult(
                success=False,
                error=f"Operation '{operation}' requires {cap_names.get(required_cap, hex(required_cap))} "
                      f"capability, AC has mask={context.capability_mask}",
            )

        # Fix H-4: emit Chronicle INTENT before execution
        if connector.chronicle_enabled and self.chronicle_client is not None:
            try:
                await self.chronicle_client.emit(
                    "CONNECTOR_INVOCATION_INTENT",
                    {
                        "connector_id": connector_id,
                        "operation": operation,
                        "principal_did": context.principal_did,
                        "gsap_context_id": context.gsap_context_id,
                    },
                )
            except Exception:
                pass  # Chronicle failure must not block invocation

        result = await connector.invoke(operation, parameters, context)

        # Emit Chronicle RESULT after execution
        if connector.chronicle_enabled and self.chronicle_client is not None:
            try:
                cid = await self.chronicle_client.emit(
                    "CONNECTOR_INVOCATION_RESULT",
                    {
                        "connector_id": connector_id,
                        "operation": operation,
                        "parameters_cid": connector.compute_parameters_cid(parameters),
                        "chronicle_session_id": context.chronicle_session_id,
                        "gsap_context_id": context.gsap_context_id,
                        "pipeline_run_id": context.pipeline_run_id,
                        "dag_id": context.dag_id,
                        "success": result.success,
                    },
                )
                result.lineage_cid = cid
            except Exception:
                pass  # Chronicle failure must not break invocation

        return result

    async def _validate_ac(self, context_id: str) -> bool:
        """Fix C-7: validate AC exists and is active in the database."""
        # Skip validation for internal context IDs (compliance gate)
        if context_id == "compliance-gate":
            return True
        try:
            from gsap_broker.db import engine
            from sqlmodel.ext.asyncio.session import AsyncSession
            async with AsyncSession(engine) as session:
                result = await session.execute(
                    text(
                        "SELECT status, expires_at FROM authorization_contexts "
                        "WHERE context_id = :ctx_id"
                    ),
                    {"ctx_id": context_id.replace("-", "")},
                )
                row = result.first()
                if not row:
                    return False
                status = row[0]
                if status not in ("authorized", "active"):
                    return False
                # Check expiry
                expires_str = row[1]
                if expires_str:
                    try:
                        expires = datetime.fromisoformat(str(expires_str))
                        if expires < datetime.now(UTC).replace(tzinfo=None):
                            return False
                    except (ValueError, TypeError):
                        pass
                return True
        except Exception as e:
            logger.warning("AC validation failed: %s", e)
            return False
