from pathlib import Path
from typing import Optional
import yaml

from .models import ContextRequest, Decision, SENSITIVITY_ORDER


class PolicyEngine:
    def __init__(self, policy_path: str):
        self.policies = self._load(policy_path)

    @classmethod
    def from_list(cls, policies: list) -> "PolicyEngine":
        """Build a PolicyEngine directly from a list of policy dicts (e.g. from DB)."""
        engine = cls.__new__(cls)
        engine.policies = policies
        return engine

    def _load(self, path: str) -> list:
        p = Path(path)
        files = sorted(p.glob("**/*.yaml")) if p.is_dir() else [p]
        policies = []
        for f in files:
            with open(f) as fh:
                data = yaml.safe_load(fh)
            # Inject industry and category from directory structure:
            # policies/<industry>/<category>.yaml
            parts = f.relative_to(p).parts if p.is_dir() else ()
            industry = parts[0] if len(parts) >= 2 else None
            category = Path(parts[1]).stem if len(parts) >= 2 else (Path(parts[0]).stem if len(parts) == 1 else None)
            for pol in data.get("policies", []):
                pol.setdefault("industry", industry)
                pol.setdefault("category", category)
                policies.append(pol)
        return policies

    def evaluate(self, request: ContextRequest) -> tuple[Decision, str, str]:
        """
        Returns (decision, policy_name, reason).
        Deny-by-default — if no policy matches, access is denied.
        """
        for policy in self.policies:
            if policy["agent_role"] != request.agent_role:
                continue

            # Denied sources take priority
            denied = policy.get("denied_sources", [])
            if request.source_id in denied:
                return (
                    Decision.DENY,
                    policy["name"],
                    f"Source '{request.source_id}' is explicitly denied for role '{request.agent_role}'",
                )

            # Task-type checks (only when task_type is provided)
            if request.task_type is not None:
                denied_tasks = policy.get("denied_tasks", [])
                if request.task_type in denied_tasks:
                    return (
                        Decision.DENY,
                        policy["name"],
                        f"Task '{request.task_type}' is explicitly denied for role '{request.agent_role}'",
                    )
                allowed_tasks = policy.get("allowed_tasks", [])
                if allowed_tasks and request.task_type not in allowed_tasks:
                    return (
                        Decision.DENY,
                        policy["name"],
                        f"Task '{request.task_type}' is not in the allowed task list for role '{request.agent_role}'",
                    )

            # Allowed sources — if list exists, source must be in it
            allowed = policy.get("allowed_sources", [])
            if allowed and request.source_id not in allowed:
                return (
                    Decision.DENY,
                    policy["name"],
                    f"Source '{request.source_id}' is not in the allowed list for role '{request.agent_role}'",
                )

            # Sensitivity ceiling check
            max_sensitivity = policy.get("max_sensitivity", "restricted")
            req_level = SENSITIVITY_ORDER.index(request.sensitivity_level.value)
            max_level = SENSITIVITY_ORDER.index(max_sensitivity)
            if req_level > max_level:
                return (
                    Decision.DENY,
                    policy["name"],
                    f"Data sensitivity '{request.sensitivity_level.value}' exceeds ceiling '{max_sensitivity}' for role '{request.agent_role}'",
                )

            return Decision.ALLOW, policy["name"], "Access granted"

        return (
            Decision.DENY,
            "default_deny",
            f"No matching policy for agent role '{request.agent_role}' — denied by default",
        )
