"""Policy loading from various sources."""

from __future__ import annotations

import logging
from pathlib import Path
from typing import TYPE_CHECKING, Any

from mergeguide.engine.rules import Rule, RuleType, Severity

from .schema import PolicyFile, PolicySchema

if TYPE_CHECKING:
    pass

logger = logging.getLogger(__name__)


class PolicyLoadError(Exception):
    """Error loading policies."""

    pass


class PolicyLoader:
    """Load policies from various sources.

    Supports loading from:
    - Local YAML files
    - Directories of YAML files
    - DynamoDB (via AWS integration)
    """

    def __init__(self) -> None:
        self.schema = PolicySchema()
        self._cached_policies: dict[str, PolicyFile] = {}

    def load_file(self, file_path: str | Path) -> PolicyFile:
        """Load a policy file from disk.

        Args:
            file_path: Path to the YAML file

        Returns:
            Parsed PolicyFile

        Raises:
            PolicyLoadError: If file cannot be loaded or is invalid
        """
        path = Path(file_path)
        if not path.exists():
            raise PolicyLoadError(f"Policy file not found: {path}")

        try:
            content = path.read_text()
        except OSError as e:
            raise PolicyLoadError(f"Cannot read policy file: {e}") from e

        errors = self.schema.validate_yaml(content)
        if errors:
            raise PolicyLoadError(
                f"Invalid policy file {path}:\n" + "\n".join(f"  - {e}" for e in errors)
            )

        return PolicyFile.from_yaml(content)

    def load_directory(self, dir_path: str | Path) -> list[PolicyFile]:
        """Load all policy files from a directory.

        Args:
            dir_path: Path to directory containing policy YAML files

        Returns:
            List of parsed PolicyFiles

        Raises:
            PolicyLoadError: If directory cannot be read
        """
        path = Path(dir_path)
        if not path.is_dir():
            raise PolicyLoadError(f"Not a directory: {path}")

        policy_files = []
        for yaml_file in path.glob("*.yaml"):
            try:
                policy_files.append(self.load_file(yaml_file))
            except PolicyLoadError as e:
                # Log warning but continue loading other files
                import logging

                logging.warning(f"Skipping invalid policy file: {e}")
                continue

        for yml_file in path.glob("*.yml"):
            try:
                policy_files.append(self.load_file(yml_file))
            except PolicyLoadError as e:
                import logging

                logging.warning(f"Skipping invalid policy file: {e}")
                continue

        return policy_files

    def load_from_dict(self, data: dict[str, Any]) -> PolicyFile:
        """Load a policy from a dictionary.

        Args:
            data: Policy data dictionary

        Returns:
            Parsed PolicyFile

        Raises:
            PolicyLoadError: If data is invalid
        """
        errors = self.schema.validate(data)
        if errors:
            raise PolicyLoadError(
                "Invalid policy data:\n" + "\n".join(f"  - {e}" for e in errors)
            )

        return PolicyFile.from_dict(data)

    def load_rules(self, org_id: str = "default") -> list[Rule]:
        """Load rules for an organization.

        This is a convenience method that loads all policies
        and converts them to Rule objects for the engine.

        Args:
            org_id: Organization ID (for future multi-tenant support)

        Returns:
            List of Rule objects
        """
        rules: list[Rule] = []

        # Check for policies in standard locations
        policy_paths = [
            Path(".mergeguide/policies"),
            Path("policies"),
            Path.home() / ".mergeguide" / "policies",
        ]

        for policy_path in policy_paths:
            if policy_path.is_dir():
                policy_files = self.load_directory(policy_path)
                for policy_file in policy_files:
                    rules.extend(self._policy_file_to_rules(policy_file))

        # Also check for single policy file
        single_policy_paths = [
            Path(".mergeguide.yaml"),
            Path(".mergeguide.yml"),
            Path("mergeguide.yaml"),
            Path("mergeguide.yml"),
        ]

        for policy_path in single_policy_paths:
            if policy_path.exists():
                try:
                    policy_file = self.load_file(policy_path)
                    rules.extend(self._policy_file_to_rules(policy_file))
                except PolicyLoadError:
                    continue

        return rules

    def _policy_file_to_rules(self, policy_file: PolicyFile) -> list[Rule]:
        """Convert a PolicyFile to a list of Rule objects."""
        rules: list[Rule] = []

        for policy in policy_file.policies:
            if not policy.enabled:
                continue

            for i, rule_def in enumerate(policy.rules):
                rule_id = f"{policy.id}-{i}" if len(policy.rules) > 1 else policy.id
                rules.append(
                    Rule(
                        id=rule_id,
                        name=policy.name,
                        pattern=rule_def.pattern,
                        message=rule_def.message,
                        severity=Severity.from_string(policy.severity),
                        rule_type=RuleType.REGEX,
                        languages=policy.languages,
                        remediation=rule_def.remediation,
                        flags=rule_def.flags,
                    )
                )

        return rules


class DynamoDBPolicyLoader(PolicyLoader):
    """Load policies from DynamoDB.

    Table schema:
    - pk: ORG#{org_id}
    - sk: POLICY#{policy_id}
    - policy_data: YAML policy content
    """

    def __init__(self, table_name: str, region: str = "us-east-1") -> None:
        super().__init__()
        self.table_name = table_name
        self.region = region
        self._dynamodb = None

    @property
    def dynamodb(self) -> Any:
        """Lazy-load DynamoDB resource."""
        if self._dynamodb is None:
            import boto3

            self._dynamodb = boto3.resource("dynamodb", region_name=self.region)
        return self._dynamodb

    @property
    def table(self) -> Any:
        """Get the DynamoDB table."""
        return self.dynamodb.Table(self.table_name)

    def load_rules(self, org_id: str = "default") -> list[Rule]:
        """Load rules for an organization from DynamoDB."""
        rules: list[Rule] = []

        try:
            response = self.table.query(
                KeyConditionExpression="pk = :pk AND begins_with(sk, :sk_prefix)",
                ExpressionAttributeValues={
                    ":pk": f"ORG#{org_id}",
                    ":sk_prefix": "POLICY#",
                },
            )

            for item in response.get("Items", []):
                policy_yaml = item.get("policy_data", "")
                if policy_yaml:
                    try:
                        policy_file = PolicyFile.from_yaml(policy_yaml)
                        rules.extend(self._policy_file_to_rules(policy_file))
                    except Exception as e:
                        logger.debug(f"Failed to parse policy from DynamoDB: {e}")
                        continue

        except Exception as e:
            import logging

            logging.warning(f"Failed to load policies from DynamoDB: {e}")

        # Also load local policies as fallback/override
        local_rules = super().load_rules(org_id)
        rules.extend(local_rules)

        return rules

    def save_policy(self, org_id: str, policy_id: str, policy_yaml: str) -> None:
        """Save a policy to DynamoDB.

        Args:
            org_id: Organization ID
            policy_id: Policy identifier
            policy_yaml: YAML policy content
        """
        # Validate before saving
        errors = self.schema.validate_yaml(policy_yaml)
        if errors:
            raise PolicyLoadError(
                "Invalid policy:\n" + "\n".join(f"  - {e}" for e in errors)
            )

        self.table.put_item(
            Item={
                "pk": f"ORG#{org_id}",
                "sk": f"POLICY#{policy_id}",
                "policy_data": policy_yaml,
            }
        )
