#!/usr/bin/env python3
# -*- coding: utf-8 -*- #
#
# Copyright 2025 Wrike Inc.
#

import json
import yaml
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, cast
from rich.console import Console
import jsonschema
from jsonschema import ValidationError as JsonSchemaValidationError


class ValidationError(Exception):
    """Custom exception for configuration validation errors."""

    def __init__(self, message: str, path: Optional[str] = None, suggestions: Optional[List[str]] = None):
        """
        Initialize validation error.

        Args:
            message: Error message
            path: Path to the invalid field (e.g., "groups[0].rules[0].record")
            suggestions: List of helpful suggestions for fixing the error
        """
        self.message = message
        self.path = path
        self.suggestions = suggestions or []
        super().__init__(self.format_message())

    def format_message(self) -> str:
        """Format the error message with path and suggestions."""
        parts = [self.message]
        if self.path:
            parts.append(f"Path: {self.path}")
        if self.suggestions:
            parts.append("Suggestions:")
            for suggestion in self.suggestions:
                parts.append(f"  - {suggestion}")
        return "\n".join(parts)


class ConfigValidator:
    """Validator for unified YAML configuration format."""

    def __init__(self, schema_path: Optional[str] = None):
        """
        Initialize configuration validator.

        Args:
            schema_path: Path to custom schema file (defaults to built-in schema)
        """
        self.console = Console(stderr=True)
        self.schema_path = schema_path or self.default_schema_path()
        self.schema = self.load_schema()

    def default_schema_path(self) -> str:
        """Get path to default built-in schema."""
        # Get the path to the schemas directory relative to this file
        current_dir = Path(__file__).parent.parent
        return str(current_dir / "schemas" / "unified.yaml")

    def load_schema(self) -> Dict[str, Any]:
        """Load schema from file (YAML or JSON)."""
        try:
            with open(self.schema_path, 'r') as f:
                if self.schema_path.endswith('.yaml') or self.schema_path.endswith('.yml'):
                    return cast(Dict[str, Any], yaml.safe_load(f))
                else:
                    return cast(Dict[str, Any], json.load(f))
        except FileNotFoundError:
            raise ValidationError(
                f"Schema file not found: {self.schema_path}",
                suggestions=["Ensure the unified schema file exists at the expected path"]
            )
        except (json.JSONDecodeError, yaml.YAMLError) as e:
            raise ValidationError(
                f"Invalid format in schema file: {e}",
                path=self.schema_path,
                suggestions=["Check the schema file for valid YAML/JSON syntax"]
            )

    def validate_config(self, config_data: Dict[str, Any]) -> None:
        """
        Validate unified configuration against schema.

        Args:
            config_data: Parsed configuration dictionary

        Raises:
            ValidationError: If validation fails
        """
        # Validate against JSON schema
        self.validate_json_schema(config_data)

    def validate_json_schema(self, config_data: Dict[str, Any]) -> None:
        """
        Validate configuration against JSON schema.

        Args:
            config_data: Configuration to validate

        Raises:
            ValidationError: If schema validation fails
        """
        try:
            jsonschema.validate(config_data, self.schema)
        except JsonSchemaValidationError as e:
            # Convert jsonschema error to our custom ValidationError
            error_path = ".".join(str(p) for p in e.absolute_path) if e.absolute_path else "root"

            # Extract suggestions based on common error types
            suggestions = []
            if "required" in str(e.message).lower():
                suggestions.append("Add the missing required field")
            elif "additional properties" in str(e.message).lower():
                suggestions.append("Remove additional properties or check schema definition")
            elif "enum" in str(e.message).lower():
                suggestions.append("Use one of the allowed enum values")
            elif "pattern" in str(e.message).lower():
                suggestions.append("Ensure the value matches the required pattern")
            else:
                suggestions.append("Check the field value and type")

            raise ValidationError(
                str(e.message),
                path=error_path,
                suggestions=suggestions
            )


class CrossReferenceValidator:
    """Validator for cross-references between configuration sections."""

    def __init__(self) -> None:
        """Initialize cross-reference validator."""
        pass

    def validate_alert_wiki_consistency(self, config: Dict[str, Any]) -> List[ValidationError]:
        """
        Validate that all alerts have corresponding wiki documentation.

        Args:
            config: Configuration to validate

        Returns:
            List of validation errors for missing documentation
        """
        errors: List[ValidationError] = []

        # Only validate if both sections exist
        if not self.should_validate_wiki_consistency(config):
            return errors

        # Get alert names from groups
        groups = config.get('groups', [])
        alert_names = ConfigAnalyzer.extract_alert_names(groups)

        # Get wiki alert names
        wiki = config.get('wiki', {})
        wiki_alert_names = ConfigAnalyzer.extract_wiki_alert_names(wiki)

        # Find alerts missing from wiki
        missing_docs = alert_names - wiki_alert_names
        if missing_docs:
            errors.append(ValidationError(
                f"Alerts missing wiki documentation: {', '.join(sorted(missing_docs))}",
                path="wiki.knowledgebase.alerts.alertings",
                suggestions=[
                    "Add documentation for each alert in the wiki.knowledgebase.alerts.alertings section",
                    "Ensure alert names match exactly between groups and wiki sections"
                ]
            ))

        return errors

    def should_validate_wiki_consistency(self, config: Dict[str, Any]) -> bool:
        """
        Check if wiki consistency validation should be performed.

        Args:
            config: Configuration to check

        Returns:
            True if both alerts and wiki knowledgebase exist
        """
        return (ConfigAnalyzer.has_alerting_rules(config) and
                ConfigAnalyzer.has_wiki_knowledgebase(config))


class ConfigAnalyzer:
    """Analyzer for extracting information from configuration."""

    @staticmethod
    def extract_alert_names(groups: List[Dict[str, Any]]) -> Set[str]:
        """
        Extract alert names from groups.

        Args:
            groups: List of rule groups

        Returns:
            Set of alert names
        """
        alert_names = set()

        for group in groups:
            if group.get('name') == 'alerting_rules':
                rules = group.get('rules', [])
                for rule in rules:
                    alert_name = rule.get('alert')
                    if alert_name:
                        alert_names.add(alert_name)

        return alert_names

    @staticmethod
    def extract_wiki_alert_names(wiki: Dict[str, Any]) -> Set[str]:
        """
        Extract alert names from wiki knowledgebase.

        Args:
            wiki: Wiki section of configuration

        Returns:
            Set of alert names in wiki
        """
        alert_names = set()

        knowledgebase = wiki.get('knowledgebase', {})
        alerts = knowledgebase.get('alerts', {})
        alertings = alerts.get('alertings', {})

        # Extract all alert names from the alertings dictionary
        alert_names.update(alertings.keys())

        return alert_names

    @staticmethod
    def has_wiki_knowledgebase(config: Dict[str, Any]) -> bool:
        """
        Check if configuration has wiki knowledgebase section.

        Args:
            config: Configuration to check

        Returns:
            True if wiki.knowledgebase.alerts.alertings exists
        """
        wiki = config.get('wiki', {})
        knowledgebase = wiki.get('knowledgebase', {})
        alerts = knowledgebase.get('alerts', {})
        alertings = alerts.get('alertings', {})

        return bool(alertings)

    @staticmethod
    def has_alerting_rules(config: Dict[str, Any]) -> bool:
        """
        Check if configuration has alerting rules.

        Args:
            config: Configuration to check

        Returns:
            True if alerting_rules group exists with rules
        """
        groups = config.get('groups', [])

        for group in groups:
            if group.get('name') == 'alerting_rules':
                rules = group.get('rules', [])
                # Check if there are any rules with 'alert' field
                for rule in rules:
                    if rule.get('alert'):
                        return True

        return False
