"""Policy serialization — converts PolicyDefinition to agentsh YAML format.

See spec Section 7.2 for the full serialization rules.
"""

from __future__ import annotations

from typing import Any

import yaml

from .schema import (
    AuditSettings,
    CommandAllowRule,
    CommandDenyRule,
    CommandRedirectRule,
    CommandRedirectTarget,
    CommandRule,
    ConnectRedirect,
    DnsRedirect,
    EnvPolicy,
    EnvRule,
    FileAllowRule,
    FileAuditRule,
    FileDenyRule,
    FileRedirectRule,
    FileRule,
    FileSoftDeleteRule,
    NetworkAllowCidrsRule,
    NetworkAllowRule,
    NetworkDenyCidrsRule,
    NetworkDenyRule,
    NetworkRedirectRule,
    NetworkRule,
    PackageRule,
    PolicyDefinition,
    ResourceLimits,
    SignalRule,
    UnixSocketRule,
)


def _as_list(v: str | list[str]) -> list[str]:
    return v if isinstance(v, list) else [v]


def _serialize_file_rules(rules: list[FileRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for i, rule in enumerate(rules):
        entry: dict[str, Any] = {"name": f"file-rule-{i}"}
        if isinstance(rule, FileAllowRule):
            entry["paths"] = _as_list(rule.allow)
            entry["decision"] = "allow"
        elif isinstance(rule, FileDenyRule):
            entry["paths"] = _as_list(rule.deny)
            entry["decision"] = "deny"
        elif isinstance(rule, FileRedirectRule):
            entry["paths"] = _as_list(rule.redirect)
            entry["decision"] = "redirect"
            entry["redirect_to"] = rule.to
        elif isinstance(rule, FileAuditRule):
            entry["paths"] = _as_list(rule.audit)
            entry["decision"] = "audit"
        elif isinstance(rule, FileSoftDeleteRule):
            entry["paths"] = _as_list(rule.soft_delete)
            entry["decision"] = "soft_delete"
        if hasattr(rule, "ops") and rule.ops is not None:
            entry["operations"] = rule.ops
        out.append(entry)
    return out


def _serialize_network_rules(rules: list[NetworkRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for i, rule in enumerate(rules):
        entry: dict[str, Any] = {"name": f"network-rule-{i}"}
        if isinstance(rule, NetworkAllowCidrsRule):
            entry["cidrs"] = rule.allow_cidrs
            entry["decision"] = "allow"
            if rule.ports:
                entry["ports"] = rule.ports
        elif isinstance(rule, NetworkDenyCidrsRule):
            entry["cidrs"] = rule.deny_cidrs
            entry["decision"] = "deny"
        elif isinstance(rule, NetworkAllowRule):
            entry["domains"] = _as_list(rule.allow)
            entry["decision"] = "allow"
            if rule.ports:
                entry["ports"] = rule.ports
        elif isinstance(rule, NetworkDenyRule):
            entry["domains"] = _as_list(rule.deny)
            entry["decision"] = "deny"
        elif isinstance(rule, NetworkRedirectRule):
            entry["domains"] = [rule.redirect]
            entry["decision"] = "redirect"
            entry["redirect_to"] = rule.to
        out.append(entry)
    return out


def _serialize_command_rules(rules: list[CommandRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for i, rule in enumerate(rules):
        entry: dict[str, Any] = {"name": f"command-rule-{i}"}
        if isinstance(rule, CommandAllowRule):
            entry["commands"] = _as_list(rule.allow)
            entry["decision"] = "allow"
        elif isinstance(rule, CommandDenyRule):
            entry["commands"] = _as_list(rule.deny)
            entry["decision"] = "deny"
        elif isinstance(rule, CommandRedirectRule):
            entry["commands"] = _as_list(rule.redirect)
            entry["decision"] = "redirect"
            if isinstance(rule.to, CommandRedirectTarget):
                entry["redirect_to"] = {"command": rule.to.cmd, "args": rule.to.args}
            else:
                entry["redirect_to"] = rule.to
        out.append(entry)
    return out


def _serialize_env_rules(rules: list[EnvRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for i, rule in enumerate(rules):
        entry: dict[str, Any] = {"name": f"env-rule-{i}", "commands": rule.commands}
        if rule.allow is not None:
            entry["allow"] = rule.allow
        if rule.deny is not None:
            entry["deny"] = rule.deny
        out.append(entry)
    return out


def _serialize_dns_redirects(rules: list[DnsRedirect]) -> list[dict[str, Any]]:
    return [{"match": r.match, "resolve_to": r.resolve_to} for r in rules]


def _serialize_connect_redirects(rules: list[ConnectRedirect]) -> list[dict[str, Any]]:
    return [{"match": r.match, "redirect_to": r.redirect_to} for r in rules]


def _serialize_package_rules(rules: list[PackageRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for rule in rules:
        match: dict[str, Any] = {}
        if rule.match.packages is not None:
            match["packages"] = rule.match.packages
        if rule.match.name_patterns is not None:
            match["name_patterns"] = rule.match.name_patterns
        if rule.match.finding_type is not None:
            match["finding_type"] = rule.match.finding_type
        if rule.match.severity is not None:
            match["severity"] = rule.match.severity
        if rule.match.reasons is not None:
            match["reasons"] = rule.match.reasons
        if rule.match.license_spdx is not None:
            match["license_spdx"] = rule.match.license_spdx.model_dump(exclude_none=True)
        if rule.match.ecosystem is not None:
            match["ecosystem"] = rule.match.ecosystem
        if rule.match.options is not None:
            match["options"] = rule.match.options
        entry: dict[str, Any] = {"match": match, "action": rule.action}
        if rule.reason is not None:
            entry["reason"] = rule.reason
        out.append(entry)
    return out


def _serialize_env_policy(policy: EnvPolicy) -> dict[str, Any]:
    out: dict[str, Any] = {}
    if policy.allow is not None:
        out["allow"] = policy.allow
    if policy.deny is not None:
        out["deny"] = policy.deny
    if policy.max_bytes is not None:
        out["max_bytes"] = policy.max_bytes
    if policy.max_keys is not None:
        out["max_keys"] = policy.max_keys
    if policy.block_iteration is not None:
        out["block_iteration"] = policy.block_iteration
    return out


def _serialize_signal_rules(rules: list[SignalRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for rule in rules:
        entry: dict[str, Any] = {
            "name": rule.name,
            "signals": rule.signals,
            "target": {"type": rule.target.type},
            "decision": rule.decision,
        }
        if rule.target.pattern is not None:
            entry["target"]["pattern"] = rule.target.pattern
        if rule.fallback is not None:
            entry["fallback"] = rule.fallback
        if rule.message is not None:
            entry["message"] = rule.message
        out.append(entry)
    return out


def _serialize_unix_socket_rules(rules: list[UnixSocketRule]) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for rule in rules:
        entry: dict[str, Any] = {
            "name": rule.name,
            "paths": rule.paths,
            "decision": rule.decision,
        }
        if rule.operations is not None:
            entry["operations"] = rule.operations
        if rule.message is not None:
            entry["message"] = rule.message
        out.append(entry)
    return out


def _serialize_resource_limits(limits: ResourceLimits) -> dict[str, Any]:
    out: dict[str, Any] = {}
    if limits.max_memory_mb is not None:
        out["max_memory_mb"] = limits.max_memory_mb
    if limits.cpu_quota_percent is not None:
        out["cpu_quota_percent"] = limits.cpu_quota_percent
    if limits.pids_max is not None:
        out["pids_max"] = limits.pids_max
    if limits.command_timeout is not None:
        out["command_timeout"] = limits.command_timeout
    if limits.session_timeout is not None:
        out["session_timeout"] = limits.session_timeout
    if limits.idle_timeout is not None:
        out["idle_timeout"] = limits.idle_timeout
    return out


def _serialize_audit_settings(settings: AuditSettings) -> dict[str, Any]:
    out: dict[str, Any] = {}
    if settings.log_allowed is not None:
        out["log_allowed"] = settings.log_allowed
    if settings.log_denied is not None:
        out["log_denied"] = settings.log_denied
    if settings.log_approved is not None:
        out["log_approved"] = settings.log_approved
    if settings.include_stdout is not None:
        out["include_stdout"] = settings.include_stdout
    if settings.include_stderr is not None:
        out["include_stderr"] = settings.include_stderr
    return out


def serialize_policy(policy: PolicyDefinition) -> str:
    """Convert a PolicyDefinition to agentsh YAML format."""
    doc: dict[str, Any] = {"version": 1, "name": "secure-sandbox-policy"}

    if policy.file is not None:
        doc["file_rules"] = _serialize_file_rules(policy.file)
    if policy.network is not None:
        doc["network_rules"] = _serialize_network_rules(policy.network)
    if policy.commands is not None:
        doc["command_rules"] = _serialize_command_rules(policy.commands)
    if policy.env is not None:
        doc["env_rules"] = _serialize_env_rules(policy.env)
    if policy.dns is not None:
        doc["dns_redirects"] = _serialize_dns_redirects(policy.dns)
    if policy.connect is not None:
        doc["connect_redirects"] = _serialize_connect_redirects(policy.connect)
    if policy.package_rules is not None:
        doc["package_rules"] = _serialize_package_rules(policy.package_rules)
    if policy.env_policy is not None:
        doc["env_policy"] = _serialize_env_policy(policy.env_policy)
    if policy.signal_rules is not None:
        doc["signal_rules"] = _serialize_signal_rules(policy.signal_rules)
    if policy.unix_socket_rules is not None:
        doc["unix_socket_rules"] = _serialize_unix_socket_rules(policy.unix_socket_rules)
    if policy.resource_limits is not None:
        doc["resource_limits"] = _serialize_resource_limits(policy.resource_limits)
    if policy.audit_settings is not None:
        doc["audit"] = _serialize_audit_settings(policy.audit_settings)

    return yaml.dump(doc, default_flow_style=False, width=1000)


def system_policy_yaml() -> str:
    """Fixed system policy protecting agentsh internals."""
    doc = {
        "version": 1,
        "name": "_system-protection",
        "file_rules": [
            {
                "name": "_system-protect-config",
                "paths": ["/etc/agentsh/**"],
                "operations": ["write", "create", "delete"],
                "decision": "deny",
                "message": "Policy files are immutable during agent execution",
            },
            {
                "name": "_system-protect-binary",
                "paths": ["/usr/local/bin/agentsh*", "/usr/bin/agentsh*"],
                "operations": ["write", "create", "delete"],
                "decision": "deny",
                "message": "agentsh binary is immutable during agent execution",
            },
            {
                "name": "_system-protect-shim-files",
                "paths": ["/usr/bin/agentsh-shell-shim", "/bin/bash", "/bin/sh"],
                "operations": ["write", "create", "delete"],
                "decision": "deny",
                "message": "Shell and shim binaries are immutable during agent execution",
            },
        ],
        "command_rules": [
            {
                "name": "_system-protect-process",
                "commands": ["kill", "killall", "pkill"],
                "args_match": ["agentsh"],
                "decision": "deny",
                "message": "Cannot terminate agentsh processes",
            },
        ],
    }
    return yaml.dump(doc, default_flow_style=False, width=1000)
