"""
ID Wispera Policy Engine
Cedar-inspired declarative policy evaluation
"""

import time
from typing import Optional

from id_wispera.types import (
    Passport,
    PolicyRule,
    PolicyCondition,
    PolicyDecision,
    PolicyEffect,
    ValidationResult,
    VisaType,
    CredentialType,
    Platform,
)


# ============================================================================
# Built-in Policy Rules
# ============================================================================

DEFAULT_POLICY_RULES: list[PolicyRule] = [
    PolicyRule(
        id="require-human-owner",
        name="Require Human Owner",
        description="All passports must have a human owner for accountability",
        condition=PolicyCondition(require_human_owner=True),
        effect=PolicyEffect.DENY,
        priority=100,
        enabled=True,
    ),
    PolicyRule(
        id="max-validity-90-days",
        name="Maximum Validity Period",
        description="Credentials should not be valid for more than 90 days",
        condition=PolicyCondition(max_validity_days=90),
        effect=PolicyEffect.DENY,
        priority=90,
        enabled=True,
    ),
    PolicyRule(
        id="max-delegation-depth-3",
        name="Maximum Delegation Depth",
        description="Credentials should not be delegated more than 3 levels deep",
        condition=PolicyCondition(max_delegation_depth=3),
        effect=PolicyEffect.DENY,
        priority=80,
        enabled=True,
    ),
    PolicyRule(
        id="privilege-visa-requires-approval",
        name="Privileged Access Requires Approval",
        description="Credentials with privilege visa type require approval",
        condition=PolicyCondition(visa_types=[VisaType.PRIVILEGE]),
        effect=PolicyEffect.REQUIRE_APPROVAL,
        priority=70,
        enabled=True,
    ),
]


# ============================================================================
# Policy Evaluation
# ============================================================================

def _condition_matches(passport: Passport, condition: PolicyCondition) -> bool:
    """Check if a condition matches a passport."""
    # Visa types filter
    if condition.visa_types and len(condition.visa_types) > 0:
        if passport.visa_type not in [v.value if isinstance(v, VisaType) else v for v in condition.visa_types]:
            return False

    # Platforms filter
    if condition.platforms and len(condition.platforms) > 0:
        platform_values = [p.value if isinstance(p, Platform) else p for p in condition.platforms]
        if not any(p in platform_values for p in passport.platforms):
            return False

    # Scopes filter
    if condition.scopes and len(condition.scopes) > 0:
        if not any(s in condition.scopes for s in passport.scope):
            return False

    # Credential types filter
    if condition.credential_types and len(condition.credential_types) > 0:
        ct_values = [c.value if isinstance(c, CredentialType) else c for c in condition.credential_types]
        if passport.credential_type not in ct_values:
            return False

    # Tags filter
    if condition.tags and len(condition.tags) > 0:
        if not any(t in condition.tags for t in passport.tags):
            return False

    # Human owner check
    if condition.require_human_owner:
        if not passport.human_owner or passport.human_owner.strip() == "":
            return True  # Condition matches (violation)

    # Delegation depth check
    if condition.max_delegation_depth is not None:
        depth = len(passport.delegation_chain) if passport.delegation_chain else 0
        if depth > condition.max_delegation_depth:
            return True  # Condition matches (violation)

    # Validity period check
    if condition.max_validity_days is not None and passport.valid_until:
        valid_from = passport.valid_from
        valid_until = passport.valid_until
        days = (valid_until - valid_from).total_seconds() / (60 * 60 * 24)
        if days > condition.max_validity_days:
            return True  # Condition matches (violation)

    # Custom condition
    if condition.custom is not None:
        return condition.custom(passport)

    return True


def _evaluate_rule(passport: Passport, rule: PolicyRule) -> bool:
    """Evaluate a policy rule against a passport."""
    if not rule.enabled:
        return False
    return _condition_matches(passport, rule.condition)


def evaluate_policy(
    passport: Passport,
    action: str,
    rules: Optional[list[PolicyRule]] = None,
) -> PolicyDecision:
    """
    Evaluate policies for a passport and action.
    Returns the policy decision.

    Args:
        passport: The passport to evaluate
        action: The action being performed
        rules: Policy rules to evaluate against (defaults to DEFAULT_POLICY_RULES)

    Returns:
        PolicyDecision with the evaluation result
    """
    if rules is None:
        rules = DEFAULT_POLICY_RULES

    # Sort rules by priority (higher first)
    sorted_rules = sorted(rules, key=lambda r: r.priority, reverse=True)

    matched_rules: list[PolicyRule] = []
    final_effect = PolicyEffect.ALLOW
    reasons: list[str] = []

    for rule in sorted_rules:
        if _evaluate_rule(passport, rule):
            matched_rules.append(rule)

            effect_val = rule.effect if isinstance(rule.effect, str) else rule.effect.value
            final_val = final_effect if isinstance(final_effect, str) else final_effect.value

            # First matching deny or require-approval takes precedence
            if effect_val == PolicyEffect.DENY.value and final_val != PolicyEffect.DENY.value:
                final_effect = PolicyEffect.DENY
                reasons.append(f"{rule.name}: {rule.description}")
            elif effect_val == PolicyEffect.REQUIRE_APPROVAL.value and final_val == PolicyEffect.ALLOW.value:
                final_effect = PolicyEffect.REQUIRE_APPROVAL
                reasons.append(f"{rule.name}: {rule.description}")

    final_val = final_effect if isinstance(final_effect, str) else final_effect.value

    return PolicyDecision(
        allowed=(final_val == PolicyEffect.ALLOW.value),
        effect=final_effect,
        matched_rules=matched_rules,
        reason=(
            "; ".join(reasons) if reasons
            else f'Action "{action}" is allowed by default policy'
        ),
    )


def validate_passport(
    passport: Passport,
    rules: Optional[list[PolicyRule]] = None,
) -> list[ValidationResult]:
    """
    Validate a passport against all policy rules.
    Returns list of violations.

    Args:
        passport: The passport to validate
        rules: Policy rules to validate against (defaults to DEFAULT_POLICY_RULES)

    Returns:
        List of ValidationResult violations
    """
    if rules is None:
        rules = DEFAULT_POLICY_RULES

    violations: list[ValidationResult] = []

    for rule in rules:
        if not rule.enabled:
            continue

        condition = rule.condition
        effect_val = rule.effect if isinstance(rule.effect, str) else rule.effect.value

        # Human owner check
        if condition.require_human_owner:
            if not passport.human_owner or passport.human_owner.strip() == "":
                violations.append(ValidationResult(
                    rule=rule,
                    violation="Passport is missing a human owner",
                    severity="error" if effect_val == PolicyEffect.DENY.value else "warning",
                ))

        # Delegation depth check
        if condition.max_delegation_depth is not None:
            depth = len(passport.delegation_chain) if passport.delegation_chain else 0
            if depth > condition.max_delegation_depth:
                violations.append(ValidationResult(
                    rule=rule,
                    violation=(
                        f"Delegation depth ({depth}) exceeds maximum "
                        f"({condition.max_delegation_depth})"
                    ),
                    severity="error" if effect_val == PolicyEffect.DENY.value else "warning",
                ))

        # Validity period check
        if condition.max_validity_days is not None and passport.valid_until:
            valid_from = passport.valid_from
            valid_until = passport.valid_until
            days = (valid_until - valid_from).total_seconds() / (60 * 60 * 24)
            if days > condition.max_validity_days:
                violations.append(ValidationResult(
                    rule=rule,
                    violation=(
                        f"Validity period ({round(days)} days) exceeds maximum "
                        f"({condition.max_validity_days} days)"
                    ),
                    severity="error" if effect_val == PolicyEffect.DENY.value else "warning",
                ))

        # Visa type restrictions
        if condition.visa_types:
            visa_values = [v.value if isinstance(v, VisaType) else v for v in condition.visa_types]
            passport_visa = passport.visa_type if isinstance(passport.visa_type, str) else passport.visa_type.value
            if passport_visa in visa_values:
                if effect_val == PolicyEffect.REQUIRE_APPROVAL.value:
                    violations.append(ValidationResult(
                        rule=rule,
                        violation=f'Visa type "{passport_visa}" requires approval',
                        severity="warning",
                    ))

    return violations


# ============================================================================
# Policy Management
# ============================================================================

def create_policy_rule(
    *,
    id: Optional[str] = None,
    name: str,
    description: str,
    condition: PolicyCondition,
    effect: PolicyEffect,
    priority: int = 50,
    enabled: bool = True,
) -> PolicyRule:
    """
    Create a custom policy rule.

    Args:
        id: Rule ID (auto-generated if not provided)
        name: Human-readable name
        description: Description of what this rule enforces
        condition: Conditions that trigger this rule
        effect: What happens when conditions are met
        priority: Priority (higher = evaluated first)
        enabled: Whether this rule is enabled

    Returns:
        A new PolicyRule
    """
    return PolicyRule(
        id=id or f"rule-{int(time.time() * 1000)}",
        name=name,
        description=description,
        condition=condition,
        effect=effect,
        priority=priority,
        enabled=enabled,
    )


def merge_policy_rules(*rule_sets: list[PolicyRule]) -> list[PolicyRule]:
    """
    Merge policy rules, with later rules overriding earlier ones by ID.

    Args:
        *rule_sets: Variable number of rule lists to merge

    Returns:
        Merged list of PolicyRule objects
    """
    rules_by_id: dict[str, PolicyRule] = {}

    for rule_set in rule_sets:
        for rule in rule_set:
            rules_by_id[rule.id] = rule

    return list(rules_by_id.values())


def get_enabled_rules(rules: list[PolicyRule]) -> list[PolicyRule]:
    """
    Filter rules by enabled status.

    Args:
        rules: List of policy rules

    Returns:
        List of enabled PolicyRule objects
    """
    return [r for r in rules if r.enabled]


# ============================================================================
# Policy Predicates
# ============================================================================

def has_valid_human_owner(passport: Passport) -> bool:
    """Check if passport has valid human owner."""
    return bool(passport.human_owner and passport.human_owner.strip() != "")


def is_within_validity_period(passport: Passport) -> bool:
    """Check if passport is within validity period."""
    from datetime import datetime

    now = datetime.utcnow()
    valid_from = passport.valid_from

    if now < valid_from:
        return False
    if passport.valid_until and now > passport.valid_until:
        return False
    return True


def has_excessive_delegation(passport: Passport, max_depth: int = 3) -> bool:
    """Check if passport has excessive delegation."""
    depth = len(passport.delegation_chain) if passport.delegation_chain else 0
    return depth > max_depth


def has_broad_scope(passport: Passport, max_scopes: int = 10) -> bool:
    """Check if passport scope is too broad."""
    return len(passport.scope) > max_scopes


def has_privileged_access(passport: Passport) -> bool:
    """Check if passport has privileged access."""
    visa = passport.visa_type if isinstance(passport.visa_type, str) else passport.visa_type.value
    return visa == "privilege"


# ============================================================================
# Policy DSL Helpers
# ============================================================================

class PolicyBuilder:
    """Builder for creating policy rules fluently."""

    def __init__(self, id: str) -> None:
        self._id = id
        self._name: Optional[str] = None
        self._description: Optional[str] = None
        self._condition = PolicyCondition()
        self._effect = PolicyEffect.ALLOW
        self._priority = 50
        self._enabled = True

    def name(self, name: str) -> "PolicyBuilder":
        """Set the rule name."""
        self._name = name
        return self

    def description(self, description: str) -> "PolicyBuilder":
        """Set the rule description."""
        self._description = description
        return self

    def for_visa_types(self, *types: VisaType) -> "PolicyBuilder":
        """Set visa type conditions."""
        self._condition.visa_types = list(types)
        return self

    def for_platforms(self, *platforms: Platform) -> "PolicyBuilder":
        """Set platform conditions."""
        self._condition.platforms = list(platforms)
        return self

    def for_credential_types(self, *types: CredentialType) -> "PolicyBuilder":
        """Set credential type conditions."""
        self._condition.credential_types = list(types)
        return self

    def require_human_owner(self) -> "PolicyBuilder":
        """Require a human owner."""
        self._condition.require_human_owner = True
        return self

    def max_delegation_depth(self, depth: int) -> "PolicyBuilder":
        """Set maximum delegation depth."""
        self._condition.max_delegation_depth = depth
        return self

    def max_validity_days(self, days: int) -> "PolicyBuilder":
        """Set maximum validity days."""
        self._condition.max_validity_days = days
        return self

    def with_custom_condition(self, fn) -> "PolicyBuilder":
        """Set a custom condition function."""
        self._condition.custom = fn
        return self

    def deny(self) -> "PolicyBuilder":
        """Set effect to deny."""
        self._effect = PolicyEffect.DENY
        return self

    def require_approval(self) -> "PolicyBuilder":
        """Set effect to require-approval."""
        self._effect = PolicyEffect.REQUIRE_APPROVAL
        return self

    def allow(self) -> "PolicyBuilder":
        """Set effect to allow."""
        self._effect = PolicyEffect.ALLOW
        return self

    def priority(self, priority: int) -> "PolicyBuilder":
        """Set the rule priority."""
        self._priority = priority
        return self

    def disabled(self) -> "PolicyBuilder":
        """Disable the rule."""
        self._enabled = False
        return self

    def build(self) -> PolicyRule:
        """Build the policy rule."""
        if not self._id or not self._name or not self._description:
            raise ValueError("Policy rule requires id, name, and description")

        return PolicyRule(
            id=self._id,
            name=self._name,
            description=self._description,
            condition=self._condition,
            effect=self._effect,
            priority=self._priority,
            enabled=self._enabled,
        )


def policy(id: str) -> PolicyBuilder:
    """
    Start building a policy rule.

    Args:
        id: The rule ID

    Returns:
        A PolicyBuilder instance
    """
    return PolicyBuilder(id)
