#!/usr/bin/env python3
"""
Quality Metrics Dashboard

Provides comprehensive quality monitoring and reporting for skills.
Tracks completeness, accuracy, coverage, and health metrics.
"""

import json
import re
from pathlib import Path
from typing import Any
from dataclasses import dataclass, field, asdict
from datetime import datetime
from enum import Enum


class MetricLevel(Enum):
    """Metric severity level."""

    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


def _json_default(obj):
    """JSON serializer for report dataclasses.

    Serializes Enums by their value ('info') so the saved report doesn't leak
    the Python repr ('MetricLevel.INFO'); falls back to str() for anything else.
    """
    if isinstance(obj, Enum):
        return obj.value
    return str(obj)


@dataclass
class QualityMetric:
    """Individual quality metric."""

    name: str
    value: float  # 0.0-1.0 (or 0-100 percentage)
    level: MetricLevel
    description: str
    suggestions: list[str] = field(default_factory=list)


@dataclass
class QualityScore:
    """Overall quality score."""

    total_score: float  # 0-100
    completeness: float  # 0-100
    accuracy: float  # 0-100
    coverage: float  # 0-100
    health: float  # 0-100
    grade: str  # A+, A, B+, B, C, D, F


@dataclass
class QualityReport:
    """Complete quality report."""

    timestamp: str
    skill_name: str
    overall_score: QualityScore
    metrics: list[QualityMetric]
    statistics: dict[str, Any]
    recommendations: list[str]
    history: list[dict[str, Any]] = field(default_factory=list)


class QualityAnalyzer:
    """
    Analyze skill quality across multiple dimensions.

    Provides comprehensive quality assessment and reporting.
    """

    # Thresholds for quality grades
    GRADE_THRESHOLDS = {
        "A+": 95,
        "A": 90,
        "A-": 85,
        "B+": 80,
        "B": 75,
        "B-": 70,
        "C+": 65,
        "C": 60,
        "C-": 55,
        "D": 50,
        "F": 0,
    }

    def __init__(self, skill_dir: Path):
        """Initialize quality analyzer."""
        self.skill_dir = Path(skill_dir)
        self.metrics: list[QualityMetric] = []
        self.statistics: dict[str, Any] = {}

    def _reference_markdown_files(self) -> list[Path]:
        """Return all markdown files under references/, including nested layouts."""
        refs_dir = self.skill_dir / "references"
        if not refs_dir.exists():
            return []
        return sorted(path for path in refs_dir.rglob("*.md") if path.is_file())

    def analyze_completeness(self) -> float:
        """
        Analyze skill completeness.

        Checks for:
        - SKILL.md exists and has content
        - References directory exists
        - Minimum documentation coverage

        Returns:
            Completeness score (0-100)
        """
        score = 0.0
        max_score = 100.0

        # SKILL.md exists (40 points)
        skill_md = self.skill_dir / "SKILL.md"
        if skill_md.exists():
            score += 40
            content = skill_md.read_text(encoding="utf-8")

            # Has substantial content (10 points)
            if len(content) > 500:
                score += 10

            # Has sections (10 points)
            if content.count("#") >= 5:
                score += 10

        # References directory (20 points)
        refs_dir = self.skill_dir / "references"
        if refs_dir.exists():
            score += 10

            # Has reference files (10 points)
            refs = self._reference_markdown_files()
            if len(refs) > 0:
                score += 10

        # Metadata/config (20 points)
        if (self.skill_dir / "skill.json").exists():
            score += 10
        if (self.skill_dir / ".skill_version.json").exists():
            score += 10

        completeness = (score / max_score) * 100

        # Add metric
        level = MetricLevel.INFO if completeness >= 70 else MetricLevel.WARNING
        suggestions = []
        if completeness < 100:
            if not skill_md.exists():
                suggestions.append("Create SKILL.md file")
            if not refs_dir.exists():
                suggestions.append("Add references directory")
            if len(suggestions) == 0:
                suggestions.append("Expand documentation coverage")

        self.metrics.append(
            QualityMetric(
                name="Completeness",
                value=completeness,
                level=level,
                description=f"Documentation completeness: {completeness:.1f}%",
                suggestions=suggestions,
            )
        )

        return completeness

    def analyze_accuracy(self) -> float:
        """
        Analyze skill accuracy.

        Checks for:
        - No broken links
        - Valid JSON/YAML
        - Consistent metadata
        - No duplicate content

        Returns:
            Accuracy score (0-100)
        """
        score = 100.0
        issues = []

        # Check for broken references
        skill_md = self.skill_dir / "SKILL.md"
        if skill_md.exists():
            content = skill_md.read_text(encoding="utf-8")

            # Check for TODO markers (deduct 5 points each, max 20)
            todo_count = content.lower().count("todo")
            if todo_count > 0:
                deduction = min(todo_count * 5, 20)
                score -= deduction
                issues.append(f"Found {todo_count} TODO markers")

            # Check for placeholder text (deduct 10)
            placeholders = [r"\blorem ipsum\b", r"\bplaceholder\b", r"\bcoming soon\b"]
            for placeholder in placeholders:
                if re.search(placeholder, content.lower()):
                    score -= 10
                    clean = placeholder.replace("\\b", "")
                    issues.append(f"Found placeholder text: {clean}")
                    break

        # Check JSON validity
        for json_file in self.skill_dir.glob("*.json"):
            try:
                json.loads(json_file.read_text())
            except json.JSONDecodeError:
                score -= 15
                issues.append(f"Invalid JSON: {json_file.name}")

        accuracy = max(score, 0.0)

        level = MetricLevel.INFO if accuracy >= 80 else MetricLevel.WARNING
        suggestions = []
        if accuracy < 100 and issues:
            suggestions.extend(issues[:3])  # Top 3 issues

        self.metrics.append(
            QualityMetric(
                name="Accuracy",
                value=accuracy,
                level=level,
                description=f"Documentation accuracy: {accuracy:.1f}%",
                suggestions=suggestions,
            )
        )

        return accuracy

    def analyze_coverage(self) -> float:
        """
        Analyze documentation coverage.

        Checks for:
        - Multiple document types
        - Code examples
        - API references
        - Getting started guide

        Returns:
            Coverage score (0-100)
        """
        score = 0.0
        max_score = 100.0

        refs_dir = self.skill_dir / "references"
        if refs_dir.exists():
            ref_files = self._reference_markdown_files()

            # Has multiple references (30 points)
            if len(ref_files) >= 3:
                score += 30
            elif len(ref_files) >= 1:
                score += 15

            # Check for specific types (20 points each)
            ref_names = [f.stem.lower() for f in ref_files]

            if any("getting" in name or "start" in name for name in ref_names):
                score += 20

            if any("api" in name or "reference" in name for name in ref_names):
                score += 20

            if any("example" in name or "tutorial" in name for name in ref_names):
                score += 20

            # Has diverse content (10 points)
            if len(ref_files) >= 5:
                score += 10

        coverage = (score / max_score) * 100

        level = MetricLevel.INFO if coverage >= 60 else MetricLevel.WARNING
        suggestions = []
        if coverage < 100:
            if coverage < 30:
                suggestions.append("Add getting started guide")
            if coverage < 60:
                suggestions.append("Add API reference documentation")
            suggestions.append("Expand documentation coverage")

        self.metrics.append(
            QualityMetric(
                name="Coverage",
                value=coverage,
                level=level,
                description=f"Documentation coverage: {coverage:.1f}%",
                suggestions=suggestions,
            )
        )

        return coverage

    def analyze_health(self) -> float:
        """
        Analyze skill health.

        Checks for:
        - File sizes reasonable
        - No empty files
        - Recent updates
        - Proper structure

        Returns:
            Health score (0-100)
        """
        score = 100.0
        issues = []

        # Check for empty files (deduct 15 each)
        for md_file in self.skill_dir.rglob("*.md"):
            if md_file.stat().st_size == 0:
                score -= 15
                issues.append(f"Empty file: {md_file.name}")

        # Check for very large files (deduct 10)
        for md_file in self.skill_dir.rglob("*.md"):
            if md_file.stat().st_size > 500_000:  # > 500KB
                score -= 10
                issues.append(f"Very large file: {md_file.name}")

        # Check directory structure (deduct 20 if missing)
        if not (self.skill_dir / "references").exists():
            score -= 20
            issues.append("Missing references directory")

        health = max(score, 0.0)

        level = MetricLevel.INFO if health >= 80 else MetricLevel.WARNING
        suggestions = []
        if health < 100:
            suggestions.extend(issues[:3])

        self.metrics.append(
            QualityMetric(
                name="Health",
                value=health,
                level=level,
                description=f"Skill health: {health:.1f}%",
                suggestions=suggestions,
            )
        )

        return health

    def calculate_statistics(self) -> dict[str, Any]:
        """Calculate skill statistics."""
        stats = {
            "total_files": 0,
            "total_size_bytes": 0,
            "markdown_files": 0,
            "reference_files": 0,
            "total_characters": 0,
            "total_words": 0,
        }

        # Count files and sizes
        for md_file in self.skill_dir.rglob("*.md"):
            stats["total_files"] += 1
            stats["markdown_files"] += 1
            size = md_file.stat().st_size
            stats["total_size_bytes"] += size

            # Count words
            try:
                content = md_file.read_text(encoding="utf-8")
                stats["total_characters"] += len(content)
                stats["total_words"] += len(content.split())
            except Exception:
                pass

        # Count references
        stats["reference_files"] = len(self._reference_markdown_files())

        self.statistics = stats
        return stats

    def calculate_overall_score(
        self, completeness: float, accuracy: float, coverage: float, health: float
    ) -> QualityScore:
        """
        Calculate overall quality score.

        Weighted average:
        - Completeness: 30%
        - Accuracy: 25%
        - Coverage: 25%
        - Health: 20%
        """
        total = completeness * 0.30 + accuracy * 0.25 + coverage * 0.25 + health * 0.20

        # Determine grade
        grade = "F"
        for g, threshold in self.GRADE_THRESHOLDS.items():
            if total >= threshold:
                grade = g
                break

        return QualityScore(
            total_score=total,
            completeness=completeness,
            accuracy=accuracy,
            coverage=coverage,
            health=health,
            grade=grade,
        )

    def generate_recommendations(self, score: QualityScore) -> list[str]:
        """Generate improvement recommendations."""
        recommendations = []

        # Priority recommendations
        if score.completeness < 70:
            recommendations.append("🔴 PRIORITY: Improve documentation completeness")

        if score.accuracy < 80:
            recommendations.append("🟡 Address accuracy issues (TODOs, placeholders)")

        if score.coverage < 60:
            recommendations.append("🟡 Expand documentation coverage (API, examples)")

        if score.health < 80:
            recommendations.append("🟡 Fix health issues (empty files, structure)")

        # General recommendations
        if score.total_score < 80:
            recommendations.append("📝 Review and enhance overall documentation quality")

        if score.total_score >= 90:
            recommendations.append("✅ Excellent quality! Consider adding advanced topics")

        return recommendations

    def generate_report(self) -> QualityReport:
        """
        Generate comprehensive quality report.

        Returns:
            Complete quality report
        """
        # Run all analyses
        completeness = self.analyze_completeness()
        accuracy = self.analyze_accuracy()
        coverage = self.analyze_coverage()
        health = self.analyze_health()

        # Calculate overall score
        overall_score = self.calculate_overall_score(completeness, accuracy, coverage, health)

        # Calculate statistics
        stats = self.calculate_statistics()

        # Generate recommendations
        recommendations = self.generate_recommendations(overall_score)

        return QualityReport(
            timestamp=datetime.now().isoformat(),
            skill_name=self.skill_dir.name,
            overall_score=overall_score,
            metrics=self.metrics,
            statistics=stats,
            recommendations=recommendations,
        )

    def format_report(self, report: QualityReport) -> str:
        """Format report as human-readable text."""
        lines = ["=" * 70]
        lines.append("QUALITY METRICS DASHBOARD")
        lines.append("=" * 70)
        lines.append("")

        # Header
        lines.append(f"📊 Skill: {report.skill_name}")
        lines.append(f"🕐 Time: {report.timestamp}")
        lines.append("")

        # Overall Score
        score = report.overall_score
        lines.append("🎯 OVERALL SCORE")
        lines.append(f"   Grade: {score.grade}")
        lines.append(f"   Score: {score.total_score:.1f}/100")
        lines.append("")

        # Component Scores
        lines.append("📈 COMPONENT SCORES")
        lines.append(f"   Completeness: {score.completeness:.1f}% (30% weight)")
        lines.append(f"   Accuracy:     {score.accuracy:.1f}% (25% weight)")
        lines.append(f"   Coverage:     {score.coverage:.1f}% (25% weight)")
        lines.append(f"   Health:       {score.health:.1f}% (20% weight)")
        lines.append("")

        # Metrics
        lines.append("📋 DETAILED METRICS")
        for metric in report.metrics:
            icon = {
                MetricLevel.INFO: "✅",
                MetricLevel.WARNING: "⚠️",
                MetricLevel.ERROR: "❌",
                MetricLevel.CRITICAL: "🔴",
            }.get(metric.level, "ℹ️")

            lines.append(f"   {icon} {metric.name}: {metric.value:.1f}%")
            if metric.suggestions:
                for suggestion in metric.suggestions[:2]:
                    lines.append(f"      → {suggestion}")
        lines.append("")

        # Statistics
        lines.append("📊 STATISTICS")
        stats = report.statistics
        lines.append(f"   Total files: {stats.get('total_files', 0)}")
        lines.append(f"   Markdown files: {stats.get('markdown_files', 0)}")
        lines.append(f"   Reference files: {stats.get('reference_files', 0)}")
        lines.append(f"   Total words: {stats.get('total_words', 0):,}")
        lines.append(f"   Total size: {stats.get('total_size_bytes', 0):,} bytes")
        lines.append("")

        # Recommendations
        if report.recommendations:
            lines.append("💡 RECOMMENDATIONS")
            for rec in report.recommendations:
                lines.append(f"   {rec}")
            lines.append("")

        lines.append("=" * 70)

        return "\n".join(lines)


def main(args=None):
    """CLI entry point for quality metrics."""
    from pathlib import Path

    from skill_seekers.cli.exit_codes import EXIT_ERROR, EXIT_SUCCESS

    if args is None:
        # Single source of flags: the central QualityParser.
        from skill_seekers.cli.parsers.quality_parser import QualityParser

        parser = QualityParser().build_standalone()
        args = parser.parse_args()

    # Analyze skill
    skill_dir = Path(args.skill_dir)
    if not skill_dir.exists():
        print(f"❌ Error: Directory not found: {skill_dir}")
        return EXIT_ERROR

    analyzer = QualityAnalyzer(skill_dir)

    # Generate report
    report = analyzer.generate_report()

    # Display report. --report prints the full breakdown; otherwise still show a
    # one-line score summary so the user doesn't have to open the JSON to learn it.
    if args.report:
        formatted = analyzer.format_report(report)
        print(formatted)
    else:
        score = report.overall_score
        print(f"\n📊 Quality Score: {score.total_score:.1f}/100 (Grade: {score.grade})")

    # Save report
    report_path = Path(args.output) if args.output else skill_dir / "quality_report.json"

    report_path.write_text(json.dumps(asdict(report), indent=2, default=_json_default))
    print(f"\n✅ Report saved: {report_path}")

    # Quality gating: only when --threshold is explicitly given. Report-only
    # invocations (the historical contract — e.g. CI steps that just want
    # quality_report.json) must keep exiting 0; unenhanced skills routinely
    # score below any sensible default. overall_score.total_score is 0-100
    # while --threshold is 0-10, so compare on the same scale.
    if args.threshold is not None:
        total_score = report.overall_score.total_score  # 0-100
        if total_score < args.threshold * 10:
            print(
                f"❌ Quality score {total_score / 10:.1f}/10 is below the "
                f"threshold of {args.threshold:.1f}/10"
            )
            return EXIT_ERROR
    return EXIT_SUCCESS


if __name__ == "__main__":
    import sys

    sys.exit(main())
