"""Integration with pytest-testmon for test impact analysis.

pytest-testmon is an open-source pytest plugin (71K+ weekly downloads)
that tracks dependencies between tests and code using coverage data.

This module provides:
- Wrapper around testmon functionality
- API for querying affected tests
- Historical failure correlation enhancement

See: https://github.com/tarpas/pytest-testmon
"""

from __future__ import annotations

import json
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Set


@dataclass
class TestmonDatabase:
    """Represents testmon's dependency database (.testmondata)."""

    path: Path
    dependencies: Dict[str, Set[str]] = None  # test_id -> set of file paths

    def load(self) -> bool:
        """Load the testmon database.

        Returns:
            True if loaded successfully
        """
        if not self.path.exists():
            return False

        # testmon stores data in SQLite format
        # We'll use subprocess to query it
        try:
            import sqlite3

            conn = sqlite3.connect(str(self.path))
            cursor = conn.cursor()

            # Query the fingerprints table for dependencies
            cursor.execute(
                "SELECT name FROM sqlite_master WHERE type='table' AND name='node_fingerprints'"
            )
            if not cursor.fetchone():
                conn.close()
                return False

            self.dependencies = {}

            # Get test dependencies
            cursor.execute("SELECT nodeid, fingerprint FROM node_fingerprints")
            for nodeid, fingerprint in cursor.fetchall():
                # Parse fingerprint to extract file dependencies
                try:
                    fp_data = json.loads(fingerprint) if fingerprint else {}
                    deps = set(fp_data.get("files", []))
                    self.dependencies[nodeid] = deps
                except (json.JSONDecodeError, TypeError):
                    pass

            conn.close()
            return True
        except Exception:
            return False


class TestmonIntegration:
    """Integration with pytest-testmon.

    Provides methods to:
    - Query affected tests for changed files
    - Build dependency database
    - Export dependencies for analysis
    """

    def __init__(self, project_root: Optional[Path] = None):
        """Initialize testmon integration.

        Args:
            project_root: Root directory of the project
        """
        self.project_root = project_root or Path.cwd()
        self.testmon_data_path = self.project_root / ".testmondata"
        self._db: Optional[TestmonDatabase] = None

    @property
    def is_available(self) -> bool:
        """Check if testmon is installed and configured."""
        try:
            result = subprocess.run(
                ["python", "-c", "import testmon"],
                capture_output=True,
                timeout=5,
            )
            return result.returncode == 0
        except Exception:
            return False

    @property
    def has_database(self) -> bool:
        """Check if testmon database exists."""
        return self.testmon_data_path.exists()

    def build_database(self) -> bool:
        """Run pytest with testmon to build dependency database.

        Returns:
            True if successful
        """
        try:
            result = subprocess.run(
                ["pytest", "--testmon", "--collect-only", "-q"],
                cwd=self.project_root,
                capture_output=True,
                timeout=300,  # 5 minutes
            )
            return result.returncode == 0
        except Exception:
            return False

    def get_affected_tests(self, changed_files: List[str]) -> List[str]:
        """Get tests affected by changed files.

        Args:
            changed_files: List of changed file paths (relative to project root)

        Returns:
            List of affected test node IDs
        """
        if not self._db:
            self._db = TestmonDatabase(self.testmon_data_path)
            if not self._db.load():
                # Fall back to running testmon directly
                return self._run_testmon_affected(changed_files)

        # Find tests that depend on changed files
        affected: Set[str] = set()
        changed_set = set(str(Path(f)) for f in changed_files)

        for test_id, deps in (self._db.dependencies or {}).items():
            if deps & changed_set:
                affected.add(test_id)

        return sorted(affected)

    def _run_testmon_affected(self, changed_files: List[str]) -> List[str]:
        """Use testmon CLI to get affected tests.

        This is a fallback when we can't parse the database directly.
        """
        try:
            # Create a temporary file with changed files
            args: List[str] = ["pytest", "--testmon", "--collect-only", "-q"]
            for f in changed_files[:50]:
                args.extend(["--changed-files", f])

            result = subprocess.run(
                args,
                cwd=self.project_root,
                capture_output=True,
                text=True,
                timeout=60,
            )

            if result.returncode != 0:
                return []

            # Parse output for test IDs
            tests = []
            for line in result.stdout.split("\n"):
                line = line.strip()
                if "::" in line and not line.startswith(("=", "-", "!")):
                    tests.append(line)

            return tests
        except Exception:
            return []

    def get_all_tests(self) -> List[str]:
        """Get all test node IDs from the database."""
        if not self._db:
            self._db = TestmonDatabase(self.testmon_data_path)
            self._db.load()

        return sorted(self._db.dependencies.keys()) if self._db.dependencies else []

    def get_test_dependencies(self, test_id: str) -> Set[str]:
        """Get file dependencies for a specific test.

        Args:
            test_id: Test node ID (e.g., "tests/test_foo.py::test_bar")

        Returns:
            Set of file paths the test depends on
        """
        if not self._db:
            self._db = TestmonDatabase(self.testmon_data_path)
            self._db.load()

        return (self._db.dependencies or {}).get(test_id, set())

    def export_dependencies(self) -> Dict[str, List[str]]:
        """Export all test dependencies as JSON-serializable dict."""
        if not self._db:
            self._db = TestmonDatabase(self.testmon_data_path)
            self._db.load()

        return {
            test_id: sorted(deps)
            for test_id, deps in (self._db.dependencies or {}).items()
        }


def get_pytest_testmon_args() -> List[str]:
    """Get pytest arguments to enable testmon.

    Returns:
        List of pytest arguments
    """
    return [
        "--testmon",  # Enable testmon
        "--testmon-nocollect",  # Don't collect on first run if db exists
    ]
