# Copyright 2024 Klira AI
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Shared type definitions for the Klira SDK v2."""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Protocol, Sequence, runtime_checkable

logger = logging.getLogger(__name__)


def _coerce_str_list(
    items: list[Any], field_name: str, policy_id: str
) -> list[str]:
    """Coerce list entries to strings, flattening dicts to 'key: value' format."""
    result: list[str] = []
    for item in items:
        if isinstance(item, str):
            result.append(item)
        elif isinstance(item, dict):
            coerced = "; ".join(f"{k}: {v}" for k, v in item.items())
            logger.warning(
                "Non-string %s entry in policy '%s' (dict coerced to '%s')",
                field_name,
                policy_id,
                coerced[:80],
            )
            result.append(coerced)
        else:
            coerced = str(item)
            logger.warning(
                "Non-string %s entry in policy '%s' (%s coerced to str)",
                field_name,
                policy_id,
                type(item).__name__,
            )
            result.append(coerced)
    return result


class PolicyAction(Enum):
    """Policy actions. Only two: block or allow."""

    BLOCK = "block"
    ALLOW = "allow"


@dataclass(frozen=True)
class Policy:
    """A single guardrails policy."""

    id: str
    patterns: list[str] = field(default_factory=list)
    domains: list[str] = field(default_factory=list)
    action: PolicyAction = PolicyAction.BLOCK
    violation_response: str | None = None
    guidelines: list[str] = field(default_factory=list)
    regulation: str | None = None
    name: str | None = None
    description: str | None = None
    policy_set: str | None = None
    audit_required: bool = False
    human_review_required: bool = False

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Policy:
        """Create a Policy from a dictionary (YAML/JSON/API response)."""
        action_str = data.get("action", "block")
        if isinstance(action_str, str):
            try:
                action = PolicyAction(action_str)
            except ValueError:
                logger.warning(
                    "Invalid policy action '%s' for policy '%s', defaulting to BLOCK",
                    action_str,
                    data.get("id", "unknown"),
                )
                action = PolicyAction.BLOCK
        else:
            action = PolicyAction.BLOCK
        return cls(
            id=data.get("id", ""),
            patterns=_coerce_str_list(
                data.get("patterns", []), "patterns", data.get("id", "unknown")
            ),
            domains=data.get("domains", []),
            action=action,
            violation_response=data.get("violation_response"),
            guidelines=_coerce_str_list(
                data.get("guidelines", []), "guidelines", data.get("id", "unknown")
            ),
            regulation=data.get("regulation"),
            name=data.get("name"),
            description=data.get("description"),
            policy_set=data.get("policy_set"),
            audit_required=data.get("audit_required", False),
            human_review_required=data.get("human_review_required", False),
        )


@dataclass(frozen=True)
class PolicyMatch:
    """Result of a single policy match during fast rules evaluation."""

    policy: Policy
    matched_pattern: str | None = None
    matched_domain: str | None = None
    match_score: float = 1.0


@dataclass(frozen=True)
class GuardrailProcessingResult:
    """Result of guardrails processing.

    Guidelines are passed explicitly via this result — NEVER via OTel context.
    (Learning #7)
    """

    allowed: bool
    action: PolicyAction
    guidelines: list[str] = field(default_factory=list)
    violation_response: str | None = None
    matched_policies: list[PolicyMatch] = field(default_factory=list)
    augmentation_applied: bool = False
    regulation: str | None = None


@runtime_checkable
class LLMFallbackEvaluator(Protocol):
    """Protocol for LLM-based policy evaluation when fast rules are inconclusive."""

    def evaluate(
        self,
        text: str,
        policies: Sequence[Policy],
        direction: str,
    ) -> GuardrailProcessingResult:
        """Evaluate text against policies using an LLM.

        Called when fast rules produce no matches and llm_fallback_enabled=True.
        Should return a GuardrailProcessingResult with the LLM's assessment.
        """
        ...
