"""
Quality assurance utilities for FMUS-VID.

This module provides tools for code quality checks, performance monitoring,
and reliability testing.
"""

import ast
import inspect
import time
from pathlib import Path
from typing import Dict, List, Set, Optional, Any, Tuple
import logging
import pylint.lint
import coverage
import memory_profiler
import psutil
import numpy as np
from dataclasses import dataclass

logger = logging.getLogger(__name__)

@dataclass
class CodeMetrics:
    """Code quality metrics."""
    lines_of_code: int
    comment_lines: int
    docstring_coverage: float
    type_hint_coverage: float
    cyclomatic_complexity: float
    maintainability_index: float
    pylint_score: float

@dataclass
class PerformanceMetrics:
    """Performance test metrics."""
    execution_time: float
    memory_usage: float
    cpu_usage: float
    peak_memory: float
    operation_count: int

class CodeAnalyzer:
    """
    Analyzes code quality and maintainability.
    """

    def __init__(self, project_root: Path):
        """
        Initialize code analyzer.

        Args:
            project_root: Root directory of the project
        """
        self.project_root = Path(project_root)

    def analyze(self) -> Dict[str, CodeMetrics]:
        """
        Analyze code quality for all Python files.

        Returns:
            Dictionary of file paths and their metrics
        """
        metrics = {}

        for file_path in self.project_root.rglob("*.py"):
            if "tests" not in str(file_path):
                try:
                    metrics[str(file_path)] = self._analyze_file(file_path)
                except Exception as e:
                    logger.error(f"Failed to analyze {file_path}: {e}")

        return metrics

    def _analyze_file(self, file_path: Path) -> CodeMetrics:
        """Analyze single Python file."""
        with open(file_path, "r", encoding="utf-8") as f:
            content = f.read()

        tree = ast.parse(content)

        # Menghitung metrik dasar
        lines_of_code = len(content.splitlines())
        comment_lines = self._count_comments(content)
        docstring_coverage = self._analyze_docstrings(tree)
        type_hint_coverage = self._analyze_type_hints(tree)
        complexity = self._calculate_complexity(tree)
        maintainability = self._calculate_maintainability(
            content, lines_of_code, complexity
        )

        # Menjalankan pylint
        pylint_score = self._run_pylint(file_path)

        return CodeMetrics(
            lines_of_code=lines_of_code,
            comment_lines=comment_lines,
            docstring_coverage=docstring_coverage,
            type_hint_coverage=type_hint_coverage,
            cyclomatic_complexity=complexity,
            maintainability_index=maintainability,
            pylint_score=pylint_score
        )

    def _count_comments(self, content: str) -> int:
        """Count number of comment lines."""
        lines = content.splitlines()
        return sum(1 for line in lines if line.strip().startswith("#"))

    def _analyze_docstrings(self, tree: ast.AST) -> float:
        """Calculate docstring coverage."""
        nodes_requiring_docstring = 0
        nodes_with_docstring = 0

        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.Module)):
                nodes_requiring_docstring += 1
                if ast.get_docstring(node):
                    nodes_with_docstring += 1

        return (
            nodes_with_docstring / nodes_requiring_docstring
            if nodes_requiring_docstring > 0
            else 1.0
        )

    def _analyze_type_hints(self, tree: ast.AST) -> float:
        """Calculate type hint coverage."""
        functions = 0
        functions_with_hints = 0

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                functions += 1
                if node.returns or any(
                    arg.annotation for arg in node.args.args
                ):
                    functions_with_hints += 1

        return (
            functions_with_hints / functions
            if functions > 0
            else 1.0
        )

    def _calculate_complexity(self, tree: ast.AST) -> float:
        """Calculate cyclomatic complexity."""
        complexity = 1

        for node in ast.walk(tree):
            if isinstance(node, (
                ast.If, ast.While, ast.For, ast.ExceptHandler,
                ast.With, ast.Assert, ast.BoolOp
            )):
                complexity += 1

        return complexity

    def _calculate_maintainability(
        self,
        content: str,
        lines: int,
        complexity: float
    ) -> float:
        """Calculate maintainability index."""
        # Menggunakan formula maintainability index yang disederhanakan
        volume = len(content.split())
        maintainability = (
            171 - 5.2 * np.log(volume) -
            0.23 * complexity -
            16.2 * np.log(lines)
        )
        return min(100, max(0, maintainability))

    def _run_pylint(self, file_path: Path) -> float:
        """Run pylint and get score."""
        from io import StringIO
        import sys

        # Menangkap output pylint
        stdout = sys.stdout
        sys.stdout = string_output = StringIO()

        try:
            pylint.lint.Run(
                [str(file_path)],
                do_exit=False
            )
            output = string_output.getvalue()

            # Mengekstrak skor
            for line in output.split("\n"):
                if "Your code has been rated at" in line:
                    score = float(line.split()[6].split("/")[0])
                    return score

        finally:
            sys.stdout = stdout

        return 0.0

class PerformanceAnalyzer:
    """
    Analyzes code performance and resource usage.
    """

    def __init__(self):
        """Initialize performance analyzer."""
        self.metrics: Dict[str, List[PerformanceMetrics]] = {}

    def measure(
        self,
        func: Any,
        *args,
        name: Optional[str] = None,
        **kwargs
    ) -> PerformanceMetrics:
        """
        Measure performance of a function.

        Args:
            func: Function to measure
            name: Optional name for the measurement
            *args: Function arguments
            **kwargs: Function keyword arguments

        Returns:
            Performance metrics
        """
        name = name or func.__name__

        # Mengukur waktu eksekusi
        start_time = time.time()
        start_memory = self._get_memory_usage()

        # Menjalankan fungsi
        result = func(*args, **kwargs)

        # Mengumpulkan metrik
        end_time = time.time()
        end_memory = self._get_memory_usage()

        metrics = PerformanceMetrics(
            execution_time=end_time - start_time,
            memory_usage=end_memory - start_memory,
            cpu_usage=self._get_cpu_usage(),
            peak_memory=self._get_peak_memory(),
            operation_count=self._count_operations(func)
        )

        # Menyimpan metrik
        if name not in self.metrics:
            self.metrics[name] = []
        self.metrics[name].append(metrics)

        return metrics

    def _get_memory_usage(self) -> float:
        """Get current memory usage in MB."""
        process = psutil.Process()
        return process.memory_info().rss / 1024 / 1024

    def _get_cpu_usage(self) -> float:
        """Get CPU usage percentage."""
        process = psutil.Process()
        return process.cpu_percent()

    def _get_peak_memory(self) -> float:
        """Get peak memory usage in MB."""
        process = psutil.Process()
        return process.memory_info().peak_wset / 1024 / 1024

    def _count_operations(self, func: Any) -> int:
        """Count number of operations in function."""
        source = inspect.getsource(func)
        tree = ast.parse(source)
        return sum(1 for _ in ast.walk(tree))

    def generate_report(self) -> str:
        """
        Generate performance report.

        Returns:
            Formatted performance report
        """
        report = ["# Performance Report\n"]

        for name, measurements in self.metrics.items():
            report.append(f"## {name}\n")

            # Menghitung statistik
            times = [m.execution_time for m in measurements]
            memories = [m.memory_usage for m in measurements]

            report.extend([
                f"- Average execution time: {np.mean(times):.3f}s",
                f"- Average memory usage: {np.mean(memories):.1f}MB",
                f"- Peak memory: {max(m.peak_memory for m in measurements):.1f}MB",
                f"- Average CPU usage: {np.mean([m.cpu_usage for m in measurements]):.1f}%",
                ""
            ])

        return "\n".join(report)

class CoverageAnalyzer:
    """
    Analyzes test coverage and generates reports.
    """

    def __init__(self, project_root: Path):
        """
        Initialize coverage analyzer.

        Args:
            project_root: Root directory of the project
        """
        self.project_root = Path(project_root)
        self.cov = coverage.Coverage()

    def start(self):
        """Start coverage measurement."""
        self.cov.start()

    def stop(self):
        """Stop coverage measurement."""
        self.cov.stop()

    def save(self):
        """Save coverage data."""
        self.cov.save()

    def generate_report(self, output_dir: Path) -> None:
        """
        Generate coverage report.

        Args:
            output_dir: Output directory for report
        """
        # HTML report
        self.cov.html_report(directory=str(output_dir / "coverage"))

        # XML report untuk CI tools
        self.cov.xml_report(outfile=str(output_dir / "coverage.xml"))

        # Membuat ringkasan
        total = self.cov.report()
        logger.info(f"Total coverage: {total:.1f}%")

def run_quality_checks(project_root: Path, output_dir: Path) -> bool:
    """
    Run all quality checks.

    Args:
        project_root: Project root directory
        output_dir: Output directory for reports

    Returns:
        True if all checks pass
    """
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    # Analisis kode
    analyzer = CodeAnalyzer(project_root)
    metrics = analyzer.analyze()

    # Menulis laporan metrik
    with open(output_dir / "code_metrics.md", "w") as f:
        f.write("# Code Quality Metrics\n\n")
        for file_path, file_metrics in metrics.items():
            f.write(f"## {file_path}\n")
            f.write(f"- Lines of code: {file_metrics.lines_of_code}\n")
            f.write(f"- Comment lines: {file_metrics.comment_lines}\n")
            f.write(f"- Docstring coverage: {file_metrics.docstring_coverage:.1%}\n")
            f.write(f"- Type hint coverage: {file_metrics.type_hint_coverage:.1%}\n")
            f.write(f"- Cyclomatic complexity: {file_metrics.cyclomatic_complexity:.1f}\n")
            f.write(f"- Maintainability index: {file_metrics.maintainability_index:.1f}\n")
            f.write(f"- Pylint score: {file_metrics.pylint_score:.1f}/10.0\n\n")

    # Menentukan apakah semua check lolos
    passed = all(
        metrics[file_path].pylint_score >= 8.0 and
        metrics[file_path].docstring_coverage >= 0.8 and
        metrics[file_path].type_hint_coverage >= 0.8 and
        metrics[file_path].maintainability_index >= 65.0
        for file_path in metrics
    )

    return passed
