"""Project consistency checker.

Verifies that a Prism-generated project is consistent with its spec:
no rogue files in _generated/ dirs, no broken extensions, no orphaned
overrides, and manifest-spec alignment.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path


@dataclass
class CheckIssue:
    """A single consistency issue found by the checker."""

    level: str  # "error" or "warning"
    category: str
    message: str
    path: str | None = None

    def __str__(self) -> str:
        prefix = f"[{self.path}] " if self.path else ""
        return f"{prefix}{self.message}"


@dataclass
class CheckResult:
    """Result of running all consistency checks."""

    issues: list[CheckIssue] = field(default_factory=list)

    @property
    def errors(self) -> list[CheckIssue]:
        return [i for i in self.issues if i.level == "error"]

    @property
    def warnings(self) -> list[CheckIssue]:
        return [i for i in self.issues if i.level == "warning"]

    @property
    def ok(self) -> bool:
        return len(self.errors) == 0


class ProjectChecker:
    """Checks project consistency with the spec and manifest."""

    def __init__(self, project_dir: Path) -> None:
        self.project_dir = project_dir

    def run_all_checks(self) -> CheckResult:
        """Run all consistency checks and return combined result."""
        result = CheckResult()
        result.issues.extend(self._check_rogue_generated_files().issues)
        result.issues.extend(self._check_broken_extensions().issues)
        result.issues.extend(self._check_orphaned_overrides().issues)
        result.issues.extend(self._check_manifest_spec_alignment().issues)
        return result

    def _check_rogue_generated_files(self) -> CheckResult:
        """Check for manually-created files in _generated/ directories."""
        from prisme.tracking.manifest import ManifestManager

        result = CheckResult()
        manifest = ManifestManager.load(self.project_dir)

        if not manifest.files:
            return result

        # Find all _generated/ directories by scanning tracked files
        generated_dirs: set[str] = set()
        for path_str in manifest.files:
            parts = path_str.split("/")
            for i, part in enumerate(parts):
                if part == "_generated":
                    generated_dirs.add("/".join(parts[: i + 1]))

        # Walk each _generated/ directory and check for untracked files
        for gen_dir_str in generated_dirs:
            gen_dir = self.project_dir / gen_dir_str
            if not gen_dir.exists():
                continue

            for file_path in gen_dir.rglob("*"):
                if file_path.is_dir():
                    continue
                rel = str(file_path.relative_to(self.project_dir))
                if rel not in manifest.files:
                    result.issues.append(
                        CheckIssue(
                            level="warning",
                            category="rogue_file",
                            message="File in _generated/ directory not tracked by manifest",
                            path=rel,
                        )
                    )

        return result

    def _check_broken_extensions(self) -> CheckResult:
        """Check that extension files have their corresponding base files."""
        from prisme.tracking.manifest import ManifestManager

        result = CheckResult()
        manifest = ManifestManager.load(self.project_dir)

        for path_str, tracked in manifest.files.items():
            if tracked.extends:
                base_path = self.project_dir / tracked.extends
                if not base_path.exists():
                    result.issues.append(
                        CheckIssue(
                            level="error",
                            category="broken_extension",
                            message=f"Base file missing: {tracked.extends}",
                            path=path_str,
                        )
                    )

        return result

    def _check_orphaned_overrides(self) -> CheckResult:
        """Check that overridden files still exist on disk."""
        from prisme.tracking.logger import OverrideLogger

        result = CheckResult()
        log = OverrideLogger.load(self.project_dir)

        for override in log.get_all():
            file_path = self.project_dir / override.path
            if not file_path.exists():
                result.issues.append(
                    CheckIssue(
                        level="warning",
                        category="orphaned_override",
                        message="Override recorded for file that no longer exists",
                        path=override.path,
                    )
                )

        return result

    def _check_manifest_spec_alignment(self) -> CheckResult:
        """Check that the manifest's spec hash matches the current spec."""
        from prisme.tracking.manifest import ManifestManager, hash_content

        result = CheckResult()
        manifest = ManifestManager.load(self.project_dir)

        if not manifest.domain_hash:
            return result

        # Try to find and hash the current spec
        spec_file = self._find_spec_file()
        if spec_file and spec_file.exists():
            current_hash = hash_content(spec_file.read_text())
            if current_hash != manifest.domain_hash:
                result.issues.append(
                    CheckIssue(
                        level="warning",
                        category="spec_changed",
                        message=(
                            "Spec has changed since last generation. "
                            "Run 'prism generate' to update generated code."
                        ),
                        path=str(spec_file),
                    )
                )

        return result

    def _find_spec_file(self) -> Path | None:
        """Find the spec file from prisme.toml or default location."""
        toml_path = self.project_dir / "prisme.toml"
        if toml_path.exists():
            try:
                from prisme.config.loader import load_prisme_config

                config = load_prisme_config(toml_path)
                return Path(config.project.spec_path)
            except Exception as e:
                import logging

                logging.getLogger("prisme").debug("Config load failed in checker: %s", e)

        default = self.project_dir / "specs" / "models.py"
        if default.exists():
            return default

        return None


__all__ = [
    "CheckIssue",
    "CheckResult",
    "ProjectChecker",
]
