"""
Unified project analysis for test gaps and code quality.

Provides:
- analyze_project: Run all analysis tools and return prioritized findings
"""

from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from .coverage import (get_complex_uncovered, get_current_branch,
                       get_new_untested_functions, get_recently_changed_files,
                       get_surviving_mutations, get_uncovered_lines,
                       score_and_sort_gaps)
from .mock_analysis import (analyze_mock_completeness,
                            analyze_mock_interface_mismatches)
from .pattern_analysis import (analyze_assertion_quality,
                               analyze_import_chains,
                               analyze_timeout_prone_patterns,
                               check_test_isolation)
from .quality import (get_dead_code, get_flaky_tests,
                      get_maintainability_index, get_security_issues,
                      get_slow_tests, get_test_patterns, get_test_structure)
from .utils import get_coverage_xml_path, run_coverage, run_test_health_check


@dataclass
class AnalysisConfig:
    """Configuration for project analysis.

    Attributes:
        test_dir: Test directory name (default: "tests")
        compare_branch: Git branch to compare against (default: "main")
        run_coverage: Whether to run coverage analysis (default: True)
        run_mutations: Whether to run mutation testing (slow, default: False)
        run_flaky_detection: Whether to detect flaky tests (slow, default: False)
        min_complexity: Minimum cyclomatic complexity to report (default: 10)
        slow_test_threshold: Seconds to consider a test slow (default: 1.0)
    """

    test_dir: str = "tests"
    compare_branch: str = "main"
    run_coverage: bool = True
    run_mutations: bool = False
    run_flaky_detection: bool = False
    min_complexity: int = 10
    slow_test_threshold: float = 1.0


@dataclass
class AnalysisReport:
    """Results from project analysis.

    All findings are sorted by priority. Check `summary` for quick overview.
    """

    # Test health
    test_health: dict = field(default_factory=dict)
    test_structure: dict = field(default_factory=dict)
    test_patterns: dict = field(default_factory=dict)

    # Coverage gaps (prioritized)
    coverage_gaps: list = field(default_factory=list)

    # Code quality issues
    dead_code: list = field(default_factory=list)
    maintainability_issues: list = field(default_factory=list)
    security_issues: list = field(default_factory=list)

    # Test quality issues
    slow_tests: list = field(default_factory=list)
    flaky_tests: list = field(default_factory=list)
    weak_assertions: dict = field(default_factory=dict)
    isolation_issues: dict = field(default_factory=dict)

    # Mock analysis
    mock_completeness: dict = field(default_factory=dict)
    mock_mismatches: dict = field(default_factory=dict)

    # Pattern analysis
    timeout_risks: dict = field(default_factory=dict)
    import_chains: dict = field(default_factory=dict)

    # Mutations (if enabled)
    surviving_mutations: list = field(default_factory=list)

    # Summary
    summary: dict = field(default_factory=dict)

    def to_dict(self) -> dict:
        """Convert report to dictionary."""
        return {
            "test_health": self.test_health,
            "test_structure": self.test_structure,
            "test_patterns": self.test_patterns,
            "coverage_gaps": self.coverage_gaps,
            "dead_code": self.dead_code,
            "maintainability_issues": self.maintainability_issues,
            "security_issues": self.security_issues,
            "slow_tests": self.slow_tests,
            "flaky_tests": self.flaky_tests,
            "weak_assertions": self.weak_assertions,
            "isolation_issues": self.isolation_issues,
            "mock_completeness": self.mock_completeness,
            "mock_mismatches": self.mock_mismatches,
            "timeout_risks": self.timeout_risks,
            "import_chains": self.import_chains,
            "surviving_mutations": self.surviving_mutations,
            "summary": self.summary,
        }


def analyze_project(
    project_path: str,
    config: Optional[AnalysisConfig] = None,
) -> AnalysisReport:
    """Run comprehensive analysis on a project.

    This function orchestrates all available analysis tools:
    - Test health check
    - Coverage analysis (uncovered lines, complex functions, new functions)
    - Code quality (dead code, maintainability, security)
    - Test quality (slow tests, flaky tests, weak assertions)
    - Mock analysis (completeness, interface mismatches)
    - Pattern analysis (timeout risks, isolation issues, import chains)
    - Mutation testing (optional, slow)

    Args:
        project_path: Path to project root
        config: Analysis configuration (uses defaults if not provided)

    Returns:
        AnalysisReport with all findings, sorted by priority

    Example:
        from nokrashi import analyze_project, AnalysisConfig

        # Quick analysis (no mutations or flaky detection)
        report = analyze_project("/path/to/project")

        # Full analysis
        config = AnalysisConfig(run_mutations=True, run_flaky_detection=True)
        report = analyze_project("/path/to/project", config)

        # Check summary
        print(report.summary)

        # Get prioritized coverage gaps
        for gap in report.coverage_gaps[:10]:
            print(f"{gap['file']}:{gap.get('line', '?')} - {gap['reason']}")
    """
    if config is None:
        config = AnalysisConfig()

    report = AnalysisReport()
    project_path = str(Path(project_path).resolve())

    # 1. Test health check
    report.test_health = run_test_health_check(project_path, config.test_dir)

    # 2. Test structure and patterns
    report.test_structure = get_test_structure(project_path, config.test_dir)
    report.test_patterns = get_test_patterns(project_path, config.test_dir)

    # 3. Coverage analysis
    all_gaps = []
    coverage_file = None

    if config.run_coverage:
        run_coverage(project_path, config.test_dir)
        coverage_file = get_coverage_xml_path(project_path)

        if coverage_file.exists():
            # Uncovered changed lines
            uncovered = get_uncovered_lines(
                project_path, config.compare_branch, coverage_file
            )
            all_gaps.extend(uncovered)

            # High complexity low coverage
            complex_gaps = get_complex_uncovered(
                project_path, coverage_file, config.min_complexity
            )
            all_gaps.extend(complex_gaps)

            # New untested functions
            new_funcs = get_new_untested_functions(
                project_path, config.compare_branch, coverage_file
            )
            all_gaps.extend(new_funcs)

    # 4. Code quality
    report.dead_code = get_dead_code(project_path)
    report.maintainability_issues = get_maintainability_index(project_path)
    report.security_issues = get_security_issues(project_path)

    # 5. Test quality
    report.slow_tests = get_slow_tests(
        project_path,
        config.test_dir,
        config.slow_test_threshold,
        report.test_health,
    )

    if config.run_flaky_detection:
        report.flaky_tests = get_flaky_tests(project_path, config.test_dir)

    report.weak_assertions = analyze_assertion_quality(project_path, config.test_dir)
    report.isolation_issues = check_test_isolation(project_path, config.test_dir)

    # 6. Mock analysis
    report.mock_completeness = analyze_mock_completeness(project_path, config.test_dir)
    report.mock_mismatches = analyze_mock_interface_mismatches(
        project_path, config.test_dir
    )

    # 7. Pattern analysis
    report.timeout_risks = analyze_timeout_prone_patterns(project_path, config.test_dir)
    report.import_chains = analyze_import_chains(project_path, config.test_dir)

    # 8. Mutation testing (optional, slow)
    if config.run_mutations:
        report.surviving_mutations = get_surviving_mutations(project_path)
        all_gaps.extend(report.surviving_mutations)

    # 9. Prioritize and sort all gaps
    recently_changed = get_recently_changed_files(project_path, config.compare_branch)
    report.coverage_gaps = score_and_sort_gaps(all_gaps, recently_changed)

    # 10. Build summary
    report.summary = _build_summary(report, config)

    return report


def _build_summary(report: AnalysisReport, config: AnalysisConfig) -> dict:
    """Build a summary of analysis findings."""
    summary = {
        "project_healthy": True,
        "total_issues": 0,
        "critical_issues": 0,
        "warnings": 0,
        "categories": {},
    }

    # Test health
    if report.test_health.get("failing_tests"):
        summary["project_healthy"] = False
        summary["critical_issues"] += len(report.test_health["failing_tests"])
        summary["categories"]["failing_tests"] = len(
            report.test_health["failing_tests"]
        )

    # Coverage gaps
    high_priority_gaps = [
        g for g in report.coverage_gaps if g.get("priority") == "high"
    ]
    if high_priority_gaps:
        summary["warnings"] += len(high_priority_gaps)
        summary["categories"]["coverage_gaps"] = len(report.coverage_gaps)

    # Dead code
    if report.dead_code:
        summary["warnings"] += len(report.dead_code)
        summary["categories"]["dead_code"] = len(report.dead_code)

    # Maintainability
    if report.maintainability_issues:
        summary["warnings"] += len(report.maintainability_issues)
        summary["categories"]["maintainability"] = len(report.maintainability_issues)

    # Security issues
    high_severity = [i for i in report.security_issues if i.get("severity") == "HIGH"]
    if high_severity:
        summary["project_healthy"] = False
        summary["critical_issues"] += len(high_severity)
    if report.security_issues:
        summary["categories"]["security"] = len(report.security_issues)

    # Slow tests
    if report.slow_tests:
        summary["categories"]["slow_tests"] = len(report.slow_tests)

    # Flaky tests
    if report.flaky_tests:
        summary["project_healthy"] = False
        summary["critical_issues"] += len(report.flaky_tests)
        summary["categories"]["flaky_tests"] = len(report.flaky_tests)

    # Weak assertions
    no_assertions = len(report.weak_assertions.get("no_assertions", []))
    if no_assertions:
        summary["warnings"] += no_assertions
        summary["categories"]["weak_assertions"] = no_assertions

    # Isolation issues
    isolation_total = report.isolation_issues.get("summary", {}).get("total_issues", 0)
    if isolation_total:
        summary["warnings"] += isolation_total
        summary["categories"]["isolation_issues"] = isolation_total

    # Mock issues
    mock_gaps = len(report.mock_completeness.get("potential_gaps", []))
    mock_mismatches = report.mock_mismatches.get("summary", {}).get(
        "total_mismatches", 0
    )
    if mock_gaps or mock_mismatches:
        summary["categories"]["mock_issues"] = mock_gaps + mock_mismatches

    # Timeout risks
    timeout_count = len(report.timeout_risks.get("timeout_risks", []))
    if timeout_count:
        summary["warnings"] += timeout_count
        summary["categories"]["timeout_risks"] = timeout_count

    # Mutations
    if report.surviving_mutations:
        summary["warnings"] += len(report.surviving_mutations)
        summary["categories"]["surviving_mutations"] = len(report.surviving_mutations)

    summary["total_issues"] = summary["critical_issues"] + summary["warnings"]

    return summary
