"""Directory scanning for trajectory viewer."""

from __future__ import annotations

from pathlib import Path

from harbor.viewer.data import (
    TrajectoryFormatter,
    TrajectoryLoader,
    TrialArtifacts,
)
from harbor.viewer.models import (
    JobSummary,
    Status,
    TrajectoryResponse,
    TrialSummary,
)


class DirectoryScanner:
    """Scans directories for jobs and trials with trajectories."""

    def __init__(self, root_path: Path):
        self.root_path = root_path.resolve()

    def scan_jobs(self) -> list[JobSummary]:
        """Scan for all jobs in the root directory.

        Returns a list of JobSummary for each job found.
        A job is identified by having trial subdirectories with trajectories.
        """
        jobs: list[JobSummary] = []

        if self._is_job_directory(self.root_path):
            jobs.append(self._create_job_summary(self.root_path))
        else:
            for sub_dir in sorted(self.root_path.iterdir(), reverse=True):
                if sub_dir.is_dir() and self._is_job_directory(sub_dir):
                    jobs.append(self._create_job_summary(sub_dir))

        return jobs

    def scan_trials(self, job_id: str) -> list[TrialSummary]:
        """Scan for all trials in a job directory."""
        job_path = self._resolve_job_path(job_id)
        if not job_path or not job_path.exists():
            return []

        trials: list[TrialSummary] = []
        for trial_dir in sorted(job_path.iterdir()):
            if trial_dir.is_dir():
                artifacts = TrialArtifacts(trial_dir)
                if artifacts.is_valid_trial():
                    trials.append(self._create_trial_summary(trial_dir, artifacts))

        return trials

    def get_trajectory_data(
        self, job_id: str, trial_id: str
    ) -> TrajectoryResponse | None:
        """Load full trajectory data for a specific trial."""
        trial_path = self._resolve_trial_path(job_id, trial_id)
        if not trial_path:
            return None

        artifacts = TrialArtifacts(trial_path)

        # No trajectory: return metadata-only response
        if not artifacts.trajectory_path.exists():
            return TrajectoryFormatter.format_metadata_only(artifacts)

        # Has trajectory: load and format with full data
        info = TrajectoryLoader.from_file(artifacts.trajectory_path)
        if not info:
            return None

        # Load additional metadata from trial artifacts
        metadata = artifacts.load_all_metadata()
        info.result = metadata.get("result")
        info.task_name = metadata.get("task_name") or info.task_name
        info.config = metadata.get("config")
        info.trial_log = metadata.get("trial_log")
        info.exception = metadata.get("exception")
        info.verifier_stdout = metadata.get("verifier_stdout")
        info.verifier_stderr = metadata.get("verifier_stderr")

        return TrajectoryFormatter.format_trajectory(info)

    def get_trajectory_mtime(self, job_id: str, trial_id: str) -> float | None:
        """Get modification time for change detection."""
        trial_path = self._resolve_trial_path(job_id, trial_id)
        if not trial_path:
            return None

        traj_path = trial_path / "agent" / "trajectory.json"
        if traj_path.exists():
            return traj_path.stat().st_mtime
        return None

    def _is_job_directory(self, path: Path) -> bool:
        """Check if a directory looks like a job directory."""
        if not path.is_dir():
            return False

        for sub in path.iterdir():
            if sub.is_dir():
                artifacts = TrialArtifacts(sub)
                if artifacts.is_valid_trial():
                    return True
        return False

    def _resolve_job_path(self, job_id: str) -> Path | None:
        """Resolve job_id to actual path."""
        if self._is_job_directory(self.root_path) and self.root_path.name == job_id:
            return self.root_path

        job_path = self.root_path / job_id
        if job_path.exists() and job_path.is_dir():
            return job_path

        return None

    def _resolve_trial_path(self, job_id: str, trial_id: str) -> Path | None:
        """Resolve job_id/trial_id to actual trial path."""
        job_path = self._resolve_job_path(job_id)
        if not job_path:
            return None

        trial_path = job_path / trial_id
        if trial_path.exists() and trial_path.is_dir():
            return trial_path

        return None

    def _create_job_summary(self, job_path: Path) -> JobSummary:
        """Create a JobSummary for a job directory."""
        trial_count = 0
        completed_count = 0
        success_count = 0
        failed_count = 0
        has_running = False

        for sub in job_path.iterdir():
            if sub.is_dir():
                artifacts = TrialArtifacts(sub)
                if artifacts.is_valid_trial():
                    trial_count += 1
                    result = artifacts.load_result()
                    if result:
                        completed_count += 1
                        rewards = result.verifier_result.rewards if result.verifier_result else None
                        if rewards and all(v > 0 for v in rewards.values()):
                            success_count += 1
                        else:
                            failed_count += 1
                    else:
                        has_running = True

        status: Status
        if completed_count > 0 and not has_running:
            status = "completed"
        elif has_running:
            status = "running"
        else:
            status = "unknown"

        mtime = None
        try:
            mtime = job_path.stat().st_mtime
        except Exception:
            pass

        return JobSummary(
            job_id=job_path.name,
            job_path=job_path,
            trial_count=trial_count,
            completed_count=completed_count,
            success_count=success_count,
            failed_count=failed_count,
            status=status,
            mtime=mtime,
        )

    def _create_trial_summary(
        self, trial_path: Path, artifacts: TrialArtifacts
    ) -> TrialSummary:
        """Create a TrialSummary for a trial directory."""
        result = artifacts.load_result()

        status: Status = "completed" if result else "running"
        rewards = None
        task_name = trial_path.name

        exception_type = None
        if result:
            task_name = result.task_name or task_name
            if result.verifier_result and result.verifier_result.rewards:
                rewards = result.verifier_result.rewards
            if result.exception_info:
                exception_type = result.exception_info.exception_type

        traj_mtime = None
        if artifacts.trajectory_path.exists():
            try:
                traj_mtime = artifacts.trajectory_path.stat().st_mtime
            except Exception:
                pass

        return TrialSummary(
            trial_id=trial_path.name,
            trial_path=trial_path,
            task_name=task_name,
            has_trajectory=artifacts.trajectory_path.exists(),
            trajectory_mtime=traj_mtime,
            status=status,
            rewards=rewards,
            exception_type=exception_type,
        )
