"""TraceabilityChecker - Verify cross-artifact references.

Contract: CNT-T2-SVC-007
Traceability: UC-005
"""

import re
from dataclasses import dataclass, field
from pathlib import Path

from laida_cli.adapters.file_system_adapter import FileSystemAdapter
from laida_cli.adapters.yaml_adapter import YamlAdapter
from laida_cli.ports.file_system_port import FileSystemPort
from laida_cli.ports.yaml_port import YamlPort


@dataclass
class TraceabilityReport:
    """Report of traceability analysis."""

    total_references: int = 0
    valid_references: int = 0
    broken_references: list[str] = field(default_factory=list)
    orphan_artifacts: list[str] = field(default_factory=list)

    @property
    def is_valid(self) -> bool:
        """Check if all references are valid."""
        return len(self.broken_references) == 0

    @property
    def coverage(self) -> float:
        """Calculate reference coverage percentage."""
        if self.total_references == 0:
            return 100.0
        return (self.valid_references / self.total_references) * 100


class TraceabilityChecker:
    """Application service for checking cross-artifact traceability."""

    # Patterns for artifact IDs
    ID_PATTERNS = {
        "R": r"R-\d{3}",
        "QAS": r"QAS-\d{3}",
        "F": r"F-\d{3}",
        "ADR": r"ADR-\d{3}",
        "BC": r"BC-\d{3}",
        "M": r"M-\d{3}",
        "TDR": r"TDR-\d{3}",
    }

    def __init__(
        self,
        yaml_port: YamlPort | None = None,
        fs_port: FileSystemPort | None = None,
    ) -> None:
        """Initialize traceability checker.

        Args:
            yaml_port: Port for YAML operations (uses default adapter if None)
            fs_port: Port for file system operations (uses default adapter if None)
        """
        self._yaml: YamlPort = yaml_port or YamlAdapter()
        self._fs: FileSystemPort = fs_port or FileSystemAdapter()

    def check_links(self, project_path: Path) -> TraceabilityReport:
        """Check all cross-artifact references.

        Args:
            project_path: Path to LAIDA project

        Returns:
            TraceabilityReport with findings
        """
        outputs_dir = project_path / "outputs"
        if not outputs_dir.exists():
            return TraceabilityReport()

        # Collect all artifact IDs
        defined_ids: set[str] = set()
        referenced_ids: set[str] = set()

        # Scan all YAML files in outputs
        for yaml_file in outputs_dir.rglob("*.yaml"):
            content = self._fs.read_file(yaml_file)

            # Find defined IDs (usually the file's own ID)
            for _prefix, pattern in self.ID_PATTERNS.items():
                for match in re.findall(pattern, content):
                    # If it's in a context suggesting definition
                    if f"id: {match}" in content or f'id: "{match}"' in content:
                        defined_ids.add(match)
                    referenced_ids.add(match)

        # Calculate broken references
        broken = referenced_ids - defined_ids
        orphans = defined_ids - referenced_ids

        return TraceabilityReport(
            total_references=len(referenced_ids),
            valid_references=len(referenced_ids) - len(broken),
            broken_references=sorted(broken),
            orphan_artifacts=sorted(orphans),
        )

    def find_orphans(self, project_path: Path) -> list[str]:
        """Find artifacts with no incoming references.

        Args:
            project_path: Path to LAIDA project

        Returns:
            List of orphan artifact IDs
        """
        report = self.check_links(project_path)
        return report.orphan_artifacts

    def generate_coverage(self, project_path: Path) -> dict[str, float | bool]:
        """Generate traceability coverage report by artifact type.

        Args:
            project_path: Path to LAIDA project

        Returns:
            Dictionary of artifact type to coverage percentage
        """
        report = self.check_links(project_path)
        return {
            "overall": report.coverage,
            "is_valid": report.is_valid,
        }
