"""
Validate command - Validate automation for submission.

This module provides validation checks for automations before
they are submitted to the ToRivers marketplace.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path

import yaml
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text

from torivers_sdk.validators import SecurityValidator

console = Console()


@dataclass
class ValidationError:
    """A validation error."""

    location: str
    message: str
    fix_hint: str | None = None
    severity: str = "error"  # "error", "warning", or "info"


@dataclass
class ValidationResult:
    """Result of validation checks."""

    passed: bool = True
    errors: list[ValidationError] = field(default_factory=list)
    warnings: list[ValidationError] = field(default_factory=list)
    score: float = 100.0

    def add_error(
        self,
        location: str,
        message: str,
        fix_hint: str | None = None,
    ) -> None:
        """Add a validation error."""
        self.errors.append(
            ValidationError(
                location=location,
                message=message,
                fix_hint=fix_hint,
                severity="error",
            )
        )
        self.passed = False

    def add_warning(
        self,
        location: str,
        message: str,
        fix_hint: str | None = None,
    ) -> None:
        """Add a validation warning."""
        self.warnings.append(
            ValidationError(
                location=location,
                message=message,
                fix_hint=fix_hint,
                severity="warning",
            )
        )

    def calculate_score(self) -> float:
        """
        Calculate validation score based on errors and warnings.

        Returns:
            Score from 0-100, where 100 is perfect
        """
        # Base deductions
        error_deduction = 15  # Per error
        warning_deduction = 5  # Per warning

        total_deduction = (len(self.errors) * error_deduction) + (
            len(self.warnings) * warning_deduction
        )

        self.score = max(0.0, 100.0 - total_deduction)
        return self.score


def validate_automation(
    strict: bool = False,
    fix: bool = False,
    output: Console | None = None,
) -> bool:
    """
    Validate the automation for submission.

    Args:
        strict: Enable strict validation mode
        fix: Attempt to auto-fix issues
        output: Optional Console for custom output (used in testing)

    Returns:
        True if validation passes, False otherwise
    """
    out = output or console

    # Check we're in an automation project
    if not _is_automation_project():
        out.print("[red]Error:[/red] Not in an automation project directory")
        return False

    out.print(
        Panel.fit(
            "[bold]Validating Automation[/bold]",
            subtitle="Checking for submission readiness",
        )
    )
    out.print()

    result = ValidationResult()

    # Run validation checks
    _validate_manifest(result)
    _validate_main_module(result)
    _validate_tests(result)
    _validate_structure(result)
    _validate_security(result)

    if strict:
        _validate_strict(result)

    # Calculate score
    result.calculate_score()

    # Display results
    display_validation_results(result, out)

    return result.passed


def _is_automation_project() -> bool:
    """Check if the current directory is an automation project."""
    cwd = Path.cwd()
    return (cwd / "automation.yaml").exists() or (cwd / "main.py").exists()


def _validate_manifest(result: ValidationResult) -> None:
    """Validate the automation manifest."""
    cwd = Path.cwd()

    # Check manifest exists
    manifest_path = cwd / "automation.yaml"

    if not manifest_path.exists():
        result.add_error(
            location="automation.yaml",
            message="Manifest file not found",
            fix_hint="Create an automation.yaml file with required metadata",
        )
        return

    # Parse manifest
    try:
        with open(manifest_path) as f:
            manifest = yaml.safe_load(f)
    except yaml.YAMLError as e:
        result.add_error(
            location=str(manifest_path.name),
            message=f"Invalid YAML: {e}",
        )
        return

    if not isinstance(manifest, dict):
        result.add_error(
            location=str(manifest_path.name),
            message="Manifest must be a YAML object",
        )
        return

    # Check required fields
    required_fields = ["name", "version", "description"]
    for field_name in required_fields:
        if field_name not in manifest:
            result.add_error(
                location=str(manifest_path.name),
                message=f"Missing required field: {field_name}",
                fix_hint=f"Add '{field_name}' to your manifest",
            )

    # Validate version format
    if "version" in manifest:
        version = manifest["version"]
        if not _is_valid_version(str(version)):
            result.add_error(
                location=f"{manifest_path.name}:version",
                message=f"Invalid version format: {version}",
                fix_hint="Use semantic versioning (e.g., 1.0.0)",
            )

    # Check input/output schemas
    if "input_schema" not in manifest:
        result.add_warning(
            location=str(manifest_path.name),
            message="No input_schema defined",
            fix_hint="Define input_schema for better documentation",
        )

    if "output_schema" not in manifest:
        result.add_warning(
            location=str(manifest_path.name),
            message="No output_schema defined",
            fix_hint="Define output_schema for better documentation",
        )


def _validate_main_module(result: ValidationResult) -> None:
    """Validate the main automation module."""
    cwd = Path.cwd()
    main_path = cwd / "main.py"

    if not main_path.exists():
        result.add_error(
            location="main.py",
            message="Main module not found",
            fix_hint="Create a main.py file with your automation class",
        )
        return

    # Read and check main.py content
    content = main_path.read_text()

    # Check for automation class
    if "class" not in content or "Automation" not in content:
        result.add_warning(
            location="main.py",
            message="No Automation subclass found",
            fix_hint="Define a class that inherits from Automation",
        )

    # Check for automation instance
    if "automation =" not in content.lower():
        result.add_warning(
            location="main.py",
            message="No 'automation' instance exported",
            fix_hint="Export an instance: automation = YourAutomation()",
        )


def _validate_tests(result: ValidationResult) -> None:
    """Validate test suite."""
    cwd = Path.cwd()
    tests_dir = cwd / "tests"

    if not tests_dir.exists():
        result.add_warning(
            location="tests/",
            message="No tests directory found",
            fix_hint="Create a tests/ directory with test files",
        )
        return

    # Check for test files
    test_files = list(tests_dir.glob("test_*.py"))
    if not test_files:
        result.add_warning(
            location="tests/",
            message="No test files found",
            fix_hint="Add test files matching pattern test_*.py",
        )


def _validate_structure(result: ValidationResult) -> None:
    """Validate project structure."""
    cwd = Path.cwd()

    # Check for requirements
    if not (cwd / "requirements.txt").exists():
        result.add_warning(
            location="requirements.txt",
            message="No requirements.txt found",
            fix_hint="Create requirements.txt with dependencies",
        )

    # Check for README
    readme_exists = (
        (cwd / "README.md").exists()
        or (cwd / "README.rst").exists()
        or (cwd / "README.txt").exists()
    )
    if not readme_exists:
        result.add_warning(
            location="README.md",
            message="No README file found",
            fix_hint="Add a README.md with documentation",
        )


def _validate_strict(result: ValidationResult) -> None:
    """Run strict validation checks."""
    cwd = Path.cwd()

    # Check for state.py
    if not (cwd / "state.py").exists():
        result.add_warning(
            location="state.py",
            message="No separate state.py file",
            fix_hint="Move state definition to state.py for clarity",
        )

    # Check nodes directory
    nodes_dir = cwd / "nodes"
    if not nodes_dir.exists():
        result.add_warning(
            location="nodes/",
            message="No nodes directory",
            fix_hint="Organize node functions in nodes/ directory",
        )


def _validate_security(result: ValidationResult) -> None:
    """Run security validation checks."""
    cwd = Path.cwd()

    # Use security validator
    security_validator = SecurityValidator()
    security_result = security_validator.validate_all(cwd)

    # Add security issues to result
    for issue in security_result.issues:
        location = issue.file
        if issue.line:
            location = f"{issue.file}:{issue.line}"

        # Map severity to fix hints
        fix_hints = {
            "S001": "Remove eval() and use safe parsing instead",
            "S002": "Remove exec() and use safe alternatives",
            "S003": "Remove compile() - use safe string parsing",
            "S004": "Remove __import__() - use standard imports",
            "S005": "Use StorageClient from SDK for file operations",
            "S006": "Use HttpClient from SDK for HTTP requests",
            "S007": "Use HttpClient from SDK for HTTP requests",
            "S008": "Use HttpClient from SDK for HTTP requests",
            "S100": "Fix syntax errors in your Python code",
            "S200": "Remove forbidden import - use SDK alternatives",
            "S201": "Remove forbidden import - use SDK alternatives",
            "S210": "Remove import not in allowed list",
            "S211": "Remove import from not in allowed list",
            "S300": "Avoid dynamic attribute access",
            "S400": "Use credential system instead of hardcoded API key",
            "S401": "Use credential system instead of hardcoded password",
            "S402": "Use credential system instead of hardcoded token",
            "S403": "Use credential system instead of cloud credentials",
            "S404": "Remove private key from source code",
            "S405": "Use credential system instead of hardcoded token",
        }

        fix_hint = fix_hints.get(issue.code, "Review security guidelines")

        if issue.severity in ("critical", "high"):
            result.add_error(
                location=location,
                message=f"[{issue.code}] {issue.message}",
                fix_hint=fix_hint,
            )
        else:
            result.add_warning(
                location=location,
                message=f"[{issue.code}] {issue.message}",
                fix_hint=fix_hint,
            )


def _is_valid_version(version: str) -> bool:
    """Check if version follows semver format."""
    import re

    pattern = r"^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$"
    return bool(re.match(pattern, version))


def display_validation_results(
    result: ValidationResult, output: Console | None = None
) -> None:
    """
    Display validation results with rich formatting.

    This function renders comprehensive validation feedback including:
    - Pass/fail status panel
    - Error table with location, error, and suggested fix columns
    - Warning table with location and warning details
    - Validation score out of 100
    - Improvement tips for scores below 80

    Args:
        result: The ValidationResult to display
        output: Optional Console for custom output (used in testing)
    """
    out = output or console

    # Display pass/fail panel
    if result.passed and not result.warnings:
        out.print(
            Panel.fit(
                "[green]✓ Validation Passed[/green]",
                title="Validation Results",
                border_style="green",
            )
        )
    elif result.passed:
        out.print(
            Panel.fit(
                "[yellow]⚠ Validation Passed with Warnings[/yellow]",
                title="Validation Results",
                border_style="yellow",
            )
        )
    else:
        out.print(
            Panel.fit(
                "[red]✗ Validation Failed[/red]",
                title="Validation Results",
                border_style="red",
            )
        )
    out.print()

    # Display errors table
    if result.errors:
        table = Table(
            title="[bold red]Errors[/bold red]",
            show_header=True,
            header_style="bold",
            border_style="red",
            expand=True,
        )
        # Keep the "Error" column wide enough to avoid wrapping short messages in
        # narrow / dumb terminals (e.g., TERM=dumb => width=80 fallback).
        table.add_column(
            "Location",
            style="cyan",
            width=18,
            no_wrap=True,
            overflow="ellipsis",
        )
        table.add_column("Error", style="red", ratio=1, overflow="fold")
        table.add_column("Fix", style="green", width=24, overflow="fold")

        for error in result.errors:
            table.add_row(
                error.location,
                error.message,
                error.fix_hint or "-",
            )

        out.print(table)
        out.print()

    # Display warnings table
    if result.warnings:
        table = Table(
            title="[bold yellow]Warnings[/bold yellow]",
            show_header=True,
            header_style="bold",
            border_style="yellow",
            expand=True,
        )
        table.add_column(
            "Location",
            style="cyan",
            width=18,
            no_wrap=True,
            overflow="ellipsis",
        )
        table.add_column("Warning", style="yellow", ratio=1, overflow="fold")
        table.add_column("Suggestion", style="dim", width=24, overflow="fold")

        for warning in result.warnings:
            table.add_row(
                warning.location,
                warning.message,
                warning.fix_hint or "-",
            )

        out.print(table)
        out.print()

    # Display validation score
    _display_score(result, out)

    # Display improvement tips if score is below 80
    if result.score < 80:
        _display_improvement_tips(result, out)


def _display_score(result: ValidationResult, output: Console) -> None:
    """Display validation score with color coding."""
    score = result.score

    # Determine color based on score
    if score >= 90:
        color = "green"
        status = "Excellent"
    elif score >= 80:
        color = "yellow"
        status = "Good"
    elif score >= 60:
        color = "orange3"
        status = "Needs Improvement"
    else:
        color = "red"
        status = "Poor"

    score_text = Text()
    score_text.append("Validation Score: ", style="bold")
    score_text.append(f"{score:.0f}", style=f"bold {color}")
    score_text.append("/100 ", style="bold")
    score_text.append(f"({status})", style=color)

    output.print(score_text)
    output.print()


def _display_improvement_tips(result: ValidationResult, output: Console) -> None:
    """Display tips for improving validation score."""
    tips: list[str] = []

    # Generate contextual tips based on issues
    if result.errors:
        error_count = len(result.errors)
        tips.append(f"Fix {error_count} error(s) to significantly improve your score")

        # Check for specific error types
        security_errors = [e for e in result.errors if "[S" in e.message]
        if security_errors:
            tips.append(
                "Security issues detected - use SDK clients instead of "
                "direct imports"
            )

    if result.warnings:
        warning_count = len(result.warnings)
        tips.append(f"Address {warning_count} warning(s) for better code quality")

        # Check for missing documentation/tests
        doc_warnings = [
            w
            for w in result.warnings
            if "schema" in w.message.lower() or "readme" in w.message.lower()
        ]
        if doc_warnings:
            tips.append("Add documentation and schemas for better discoverability")

        test_warnings = [w for w in result.warnings if "test" in w.message.lower()]
        if test_warnings:
            tips.append("Add comprehensive tests to increase reliability")

    # General tips
    if not tips:
        tips.append("Review warnings and consider implementing suggested fixes")

    # Display tips panel
    tip_lines = "\n".join(f"• {tip}" for tip in tips[:5])  # Max 5 tips
    output.print(
        Panel(
            f"[yellow]{tip_lines}[/yellow]",
            title="[bold]💡 Tips to Improve Score[/bold]",
            border_style="yellow",
        )
    )
    output.print()


def _display_results(result: ValidationResult) -> None:
    """
    Legacy display function - use display_validation_results instead.

    This function is kept for backward compatibility.
    """
    display_validation_results(result, console)
