import logging
import re
from pathlib import Path
from typing import Optional

from rich.console import Console
from rich.table import Table

# Configure logging
logger = logging.getLogger(__name__)


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

    def __init__(
        self,
        message: str,
        file_path: Optional[Path] = None,
        line_number: Optional[int] = None,
        check_type: Optional[str] = None,
    ):
        super().__init__(message)
        self.file_path = file_path
        self.line_number = line_number
        self.check_type = check_type

    def __str__(self) -> str:
        context_parts = []
        if self.file_path:
            context_parts.append(f"File: {self.file_path}")
        if self.line_number:
            context_parts.append(f"Line: {self.line_number}")
        if self.check_type:
            context_parts.append(f"Check: {self.check_type}")

        if context_parts:
            return f"{super().__str__()} ({', '.join(context_parts)})"
        return super().__str__()


class ClaudeValidator:
    def __init__(self, document_path: Path):
        self.document_path = Path(document_path)
        self.console = Console()
        self.validation_results: list[dict[str, str]] = []
        logger.info(f"Initialized validator for document: {document_path}")

    def validate_document(self) -> bool:
        """Run all validation checks on the document."""
        logger.info(f"Starting validation for document: {self.document_path}")

        try:
            if not self.document_path.exists():
                error_msg = f"Document not found: {self.document_path}"
                logger.error(error_msg)
                self.console.print(f"[red]{error_msg}[/red]")
                return False

            content = self.document_path.read_text(encoding="utf-8")
            logger.debug(f"Read document content: {len(content)} characters")

        except (OSError, UnicodeDecodeError) as e:
            error_msg = f"Failed to read document: {e}"
            logger.error(error_msg)
            self.console.print(f"[red]{error_msg}[/red]")
            return False

        # Run validation checks
        checks = [
            ("Structure", self._validate_structure),
            ("Agent References", self._validate_agent_references),
            ("Artifact References", self._validate_artifact_references),
            ("Token Optimization", self._validate_token_optimization),
            ("Hook Syntax", self._validate_hooks_syntax),
        ]

        all_passed = True
        for check_name, check_func in checks:
            try:
                logger.debug(f"Running validation check: {check_name}")
                passed = check_func(content)
                all_passed = all_passed and passed
                logger.debug(
                    f"Validation check {check_name}: {'PASSED' if passed else 'FAILED'}"
                )
            except Exception as e:
                logger.error(
                    f"Validation check {check_name} failed with exception: {e}"
                )
                self.validation_results.append(
                    {
                        "check": check_name,
                        "status": "ERROR",
                        "details": f"Exception during validation: {e}",
                    }
                )
                all_passed = False

        self._display_results()

        result_summary = f"Validation {'PASSED' if all_passed else 'FAILED'}"
        logger.info(f"{result_summary} for document: {self.document_path}")

        return all_passed

    def _validate_structure(self, content: str) -> bool:
        """Validate document structure and required sections."""
        required_sections = [
            "## Core Mission",
            "## Fundamental Principles",
            "## Agent Specifications",
            "## Communication & Artifact Formats",
            "## Execution Workflow",
        ]

        missing_sections = []
        for section in required_sections:
            if section not in content:
                missing_sections.append(section)

        if missing_sections:
            self.validation_results.append(
                {
                    "check": "Structure",
                    "status": "FAIL",
                    "details": f"Missing sections: {', '.join(missing_sections)}",
                }
            )
            return False

        self.validation_results.append(
            {
                "check": "Structure",
                "status": "PASS",
                "details": "All required sections present",
            }
        )
        return True

    def _validate_agent_references(self, content: str) -> bool:
        """Validate that all agent references are consistent."""
        # Extract agent names from Agent Specifications section
        agent_pattern = r"### (\w+) \("
        agents = re.findall(agent_pattern, content)

        # Find all @agent_name references (excluding template placeholders)
        # Match @word: but exclude @{anything}:
        reference_pattern = r"@(?!\{)(\w+):"
        references = re.findall(reference_pattern, content)

        # Check for undefined agent references
        # Some shorthand artifact or placeholder references (e.g. @openapi:v2) are
        # intentionally not prefixed with "artifacts/" in the reference document.
        # Treat these as acceptable and do not flag them as undefined agents.
        allowed_non_agent_refs = {
            "openapi",
            "datamodel",
            "mockup",
            "ui_prototype",
            "threatmodel",
            "agent_name",  # placeholder used in snippets
            "artifact",  # used in format templates like @{artifact_path}
        }

        undefined_refs = [
            ref
            for ref in references
            if ref not in agents
            and ref != "artifacts"
            and ref not in allowed_non_agent_refs
        ]

        if undefined_refs:
            self.validation_results.append(
                {
                    "check": "Agent References",
                    "status": "FAIL",
                    "details": (
                        f"Undefined agent references: {', '.join(set(undefined_refs))}"
                    ),
                }
            )
            return False

        self.validation_results.append(
            {
                "check": "Agent References",
                "status": "PASS",
                "details": f"All agent references valid. Found {len(agents)} agents.",
            }
        )
        return True

    def _validate_artifact_references(self, content: str) -> bool:
        """Validate artifact reference format."""
        # Detect valid artifact references of the form @artifacts/name:tag
        artifact_pattern = r"@artifacts/[\w_-]+:[\w_-]+"
        valid_artifacts = re.findall(artifact_pattern, content)

        # Malformed = starts with @artifacts/ but lacks a tag (':' and name)
        # Use negative lookahead that ensures ':' does NOT follow immediately
        # after the matched name
        malformed_pattern = r"@artifacts/[\w_-]+(?!:[\w_-]+)"
        # To avoid counting the prefix of a valid reference (which overlaps),
        # we first temporarily replace the valid refs with a placeholder token.
        temp_content = content
        for ref in valid_artifacts:
            temp_content = temp_content.replace(ref, "__VALID_ARTIFACT__")

        malformed_artifacts = re.findall(malformed_pattern, temp_content)

        if malformed_artifacts:
            self.validation_results.append(
                {
                    "check": "Artifact References",
                    "status": "FAIL",
                    "details": (
                        f"Malformed artifact references: "
                        f"{', '.join(set(malformed_artifacts))}"
                    ),
                }
            )
            return False

        self.validation_results.append(
            {
                "check": "Artifact References",
                "status": "PASS",
                "details": f"Found {len(valid_artifacts)} valid artifact references",
            }
        )
        return True

    def _validate_token_optimization(self, content: str) -> bool:
        """Validate token optimization guidelines are followed."""
        # Check for token budget percentages
        budget_pattern = r"(\d+)% budget"
        budgets = re.findall(budget_pattern, content)

        if budgets:
            total_budget = sum(int(b) for b in budgets)
            if total_budget != 100:
                self.validation_results.append(
                    {
                        "check": "Token Budget",
                        "status": "FAIL",
                        "details": (
                            f"Token budget totals {total_budget}%, should be 100%"
                        ),
                    }
                )
                return False

        self.validation_results.append(
            {
                "check": "Token Budget",
                "status": "PASS",
                "details": "Token budget allocation is correct",
            }
        )
        return True

    def _validate_hooks_syntax(self, content: str) -> bool:
        """Validate shell script syntax in hooks."""
        # Extract shell script blocks
        script_pattern = r"```bash\n(.*?)\n```"
        scripts = re.findall(script_pattern, content, re.DOTALL)

        invalid_scripts = []
        for i, script in enumerate(scripts):
            # Basic syntax validation
            if not script.strip().startswith("#!/bin/bash"):
                invalid_scripts.append(f"Script {i + 1}: Missing shebang")

            # Check for dangerous commands
            dangerous_commands = ["rm -rf /", "sudo", "chmod 777"]
            for cmd in dangerous_commands:
                if cmd in script:
                    invalid_scripts.append(
                        f"Script {i + 1}: Contains dangerous command: {cmd}"
                    )

        if invalid_scripts:
            self.validation_results.append(
                {
                    "check": "Hook Syntax",
                    "status": "FAIL",
                    "details": "; ".join(invalid_scripts),
                }
            )
            return False

        self.validation_results.append(
            {
                "check": "Hook Syntax",
                "status": "PASS",
                "details": f"Validated {len(scripts)} shell scripts",
            }
        )
        return True

    def _display_results(self) -> None:
        """Display validation results in a formatted table."""
        table = Table(title="Claude.md Validation Results")
        table.add_column("Check", style="cyan")
        table.add_column("Status", style="green")
        table.add_column("Details", style="yellow")

        for result in self.validation_results:
            status = result["status"]
            if status == "PASS":
                status_color = "green"
            elif status == "ERROR":
                status_color = "magenta"
            else:  # FAIL
                status_color = "red"

            table.add_row(
                result["check"],
                f"[{status_color}]{status}[/{status_color}]",
                result["details"],
            )

        self.console.print(table)

    def _add_validation_result(
        self,
        check_name: str,
        status: str,
        details: str,
        line_number: Optional[int] = None,
    ) -> None:
        """Add a validation result with optional line number context."""
        result = {"check": check_name, "status": status, "details": details}

        if line_number:
            result["details"] = f"Line {line_number}: {details}"

        self.validation_results.append(result)

        # Log the validation result
        log_level = logging.DEBUG if status == "PASS" else logging.WARNING
        logger.log(log_level, f"Validation {check_name}: {status} - {details}")

    def _find_line_number(self, content: str, search_text: str) -> Optional[int]:
        """Find the line number where specific text appears."""
        lines = content.split("\n")
        for i, line in enumerate(lines, 1):
            if search_text in line:
                return i
        return None
