# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""Policy YAML schema validation."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any

import jsonschema
import yaml


class RuleType(Enum):
    """Type of rule (regex or DSL)."""

    REGEX = "regex"
    DSL = "dsl"


# JSON Schema for policy files
POLICY_SCHEMA: dict[str, Any] = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "required": ["version", "policies"],
    "properties": {
        "version": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+$",
            "description": "Schema version (e.g., '1.0')",
        },
        "metadata": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "description": {"type": "string"},
                "author": {"type": "string"},
                "url": {"type": "string", "format": "uri"},
            },
        },
        "policies": {
            "type": "array",
            "minItems": 1,
            "items": {
                "type": "object",
                "required": ["id", "name", "rules"],
                "properties": {
                    "id": {
                        "type": "string",
                        "pattern": "^[a-z][a-z0-9-]*$",
                        "description": "Unique policy identifier",
                    },
                    "name": {
                        "type": "string",
                        "description": "Human-readable policy name",
                    },
                    "description": {
                        "type": "string",
                        "description": "Policy description",
                    },
                    "severity": {
                        "type": "string",
                        "enum": ["error", "warning", "info"],
                        "default": "warning",
                    },
                    "languages": {
                        "type": "array",
                        "items": {"type": "string"},
                        "default": ["*"],
                        "description": "Languages this policy applies to",
                    },
                    "enabled": {
                        "type": "boolean",
                        "default": True,
                    },
                    "rules": {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                            "type": "object",
                            "required": ["message"],
                            "properties": {
                                "type": {
                                    "type": "string",
                                    "enum": ["regex", "dsl"],
                                    "default": "regex",
                                    "description": "Rule type (regex or dsl)",
                                },
                                "pattern": {
                                    "type": "string",
                                    "description": "Regex pattern to match (for regex rules)",
                                },
                                "patterns": {
                                    "type": "array",
                                    "description": "Named patterns for DSL rules",
                                    "items": {
                                        "type": "object",
                                        "required": ["pattern"],
                                        "properties": {
                                            "name": {
                                                "type": "string",
                                                "description": "Pattern name (default: 'match')",
                                            },
                                            "pattern": {
                                                "type": "string",
                                                "description": "Regex pattern with named groups",
                                            },
                                            "flags": {
                                                "type": "string",
                                                "description": "Regex flags",
                                            },
                                            "multiline": {
                                                "type": "boolean",
                                                "description": "Enable multiline matching",
                                            },
                                        },
                                    },
                                },
                                "where": {
                                    "type": "string",
                                    "description": "DSL condition using captured variables and functions",
                                },
                                "combine": {
                                    "type": "string",
                                    "enum": ["all", "any", "sequence"],
                                    "default": "all",
                                    "description": "How to combine multiple patterns",
                                },
                                "flags": {
                                    "type": "string",
                                    "pattern": "^[imsx]*$",
                                    "default": "",
                                    "description": "Regex flags (i=case, m=multiline, s=dotall)",
                                },
                                "message": {
                                    "type": "string",
                                    "description": "Message to display on match (supports ${var} interpolation)",
                                },
                                "remediation": {
                                    "type": "string",
                                    "description": "How to fix this issue",
                                },
                                "condition": {
                                    "type": "string",
                                    "description": "Condition expression for when to apply this rule",
                                },
                                "severity": {
                                    "type": "string",
                                    "enum": ["error", "warning", "info"],
                                    "description": "Override policy severity for this rule",
                                },
                            },
                        },
                    },
                    "condition": {
                        "type": "string",
                        "description": "Condition expression for when to apply this policy",
                    },
                },
            },
        },
    },
}


@dataclass
class DSLPatternDef:
    """A named pattern definition for DSL rules."""

    pattern: str
    name: str = "match"
    flags: str = ""
    multiline: bool = False


@dataclass
class PolicyRule:
    """A single rule within a policy.

    Supports two rule types:
    - regex: Simple pattern matching (default)
    - dsl: Advanced rules with variables, functions, and multi-pattern logic
    """

    message: str
    rule_type: RuleType = RuleType.REGEX
    # Regex rule fields
    pattern: str = ""
    flags: str = ""
    # DSL rule fields
    patterns: list[DSLPatternDef] = field(default_factory=list)
    where: str = ""
    combine: str = "all"
    # Common fields
    remediation: str = ""
    condition: str = ""
    # Optional severity override (if set, overrides policy-level severity)
    severity: str = ""
    # CWE identifier (e.g., "CWE-89") for vulnerability classification (BUG-401)
    cwe_id: str | None = None


@dataclass
class Policy:
    """A single policy definition."""

    id: str
    name: str
    rules: list[PolicyRule]
    description: str = ""
    severity: str = "warning"
    languages: list[str] = field(default_factory=lambda: ["*"])
    enabled: bool = True
    condition: str = ""


@dataclass
class PolicyMetadata:
    """Metadata for a policy file."""

    name: str = ""
    description: str = ""
    author: str = ""
    url: str = ""


@dataclass
class PolicyFile:
    """A complete policy file."""

    version: str
    policies: list[Policy]
    metadata: PolicyMetadata = field(default_factory=PolicyMetadata)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> PolicyFile:
        """Create a PolicyFile from a dictionary."""
        metadata_data = data.get("metadata", {})
        metadata = PolicyMetadata(
            name=metadata_data.get("name", ""),
            description=metadata_data.get("description", ""),
            author=metadata_data.get("author", ""),
            url=metadata_data.get("url", ""),
        )

        policies = []
        for policy_data in data.get("policies", []):
            rules = []
            for rule in policy_data.get("rules", []):
                rule_type_str = rule.get("type", "regex")
                rule_type = RuleType.DSL if rule_type_str == "dsl" else RuleType.REGEX

                # Parse DSL patterns if present
                dsl_patterns = []
                if "patterns" in rule:
                    for p in rule["patterns"]:
                        if isinstance(p, str):
                            dsl_patterns.append(DSLPatternDef(pattern=p))
                        elif isinstance(p, dict):
                            dsl_patterns.append(
                                DSLPatternDef(
                                    pattern=p["pattern"],
                                    name=p.get("name", "match"),
                                    flags=p.get("flags", ""),
                                    multiline=p.get("multiline", False),
                                )
                            )

                rules.append(
                    PolicyRule(
                        message=rule["message"],
                        rule_type=rule_type,
                        pattern=rule.get("pattern", ""),
                        flags=rule.get("flags", ""),
                        patterns=dsl_patterns,
                        where=rule.get("where", ""),
                        combine=rule.get("combine", "all"),
                        remediation=rule.get("remediation", ""),
                        condition=rule.get("condition", ""),
                        severity=rule.get("severity", ""),
                    )
                )
            policies.append(
                Policy(
                    id=policy_data["id"],
                    name=policy_data["name"],
                    rules=rules,
                    description=policy_data.get("description", ""),
                    severity=policy_data.get("severity", "warning"),
                    languages=policy_data.get("languages", ["*"]),
                    enabled=policy_data.get("enabled", True),
                    condition=policy_data.get("condition", ""),
                )
            )

        return cls(
            version=data["version"],
            policies=policies,
            metadata=metadata,
        )

    @classmethod
    def from_yaml(cls, yaml_content: str) -> PolicyFile:
        """Parse a policy file from YAML content."""
        data = yaml.safe_load(yaml_content)
        return cls.from_dict(data)


class PolicySchema:
    """Validator for policy files."""

    def __init__(self) -> None:
        self.validator = jsonschema.Draft7Validator(POLICY_SCHEMA)

    def validate(self, data: dict[str, Any]) -> list[str]:
        """Validate a policy dictionary.

        Args:
            data: Policy data to validate

        Returns:
            List of validation error messages (empty if valid)
        """
        errors = []
        for error in self.validator.iter_errors(data):
            path = ".".join(str(p) for p in error.absolute_path)
            if path:
                errors.append(f"{path}: {error.message}")
            else:
                errors.append(error.message)
        return errors

    def validate_yaml(self, yaml_content: str) -> list[str]:
        """Validate a YAML policy file.

        Args:
            yaml_content: YAML content to validate

        Returns:
            List of validation error messages (empty if valid)
        """
        try:
            data = yaml.safe_load(yaml_content)
        except yaml.YAMLError as e:
            return [f"YAML parsing error: {e}"]

        if not isinstance(data, dict):
            return ["Policy file must be a YAML object"]

        return self.validate(data)

    def is_valid(self, data: dict[str, Any]) -> bool:
        """Check if a policy dictionary is valid."""
        return len(self.validate(data)) == 0


def validate_policy_file(yaml_content: str) -> tuple[bool, list[str]]:
    """Validate a policy YAML file.

    Args:
        yaml_content: YAML content to validate

    Returns:
        Tuple of (is_valid, error_messages)
    """
    schema = PolicySchema()
    errors = schema.validate_yaml(yaml_content)
    return len(errors) == 0, errors
