from abc import ABC, abstractmethod
from pathlib import Path

from packaging.version import InvalidVersion, Version

from harbor.models.dataset_item import DownloadedDatasetItem
from harbor.models.registry import DatasetSpec
from harbor.tasks.client import TaskClient


def resolve_version(versions: list[str]) -> str:
    """
    Resolve the best version from a list of versions.

    Priority:
    1. "head" if it exists
    2. Highest semantic version (using PEP 440)
    3. Lexically last version
    """
    if not versions:
        raise ValueError("No versions available")

    if "head" in versions:
        return "head"

    semver_versions: list[tuple[Version, str]] = []
    for v in versions:
        try:
            semver_versions.append((Version(v), v))
        except InvalidVersion:
            pass

    if semver_versions:
        semver_versions.sort(key=lambda x: x[0], reverse=True)
        return semver_versions[0][1]

    return sorted(versions, reverse=True)[0]


class BaseRegistryClient(ABC):
    def __init__(self, url: str | None = None, path: Path | None = None):
        self._url = url
        self._path = path
        self._task_client = TaskClient()

    @abstractmethod
    def _get_dataset_spec(self, name: str, version: str) -> DatasetSpec:
        """Internal method to get a dataset spec by name and version."""
        pass

    @abstractmethod
    def get_datasets(self) -> list[DatasetSpec]:
        """Get all datasets available in the registry."""
        pass

    @abstractmethod
    def get_dataset_versions(self, name: str) -> list[str]:
        """Get all available versions for a dataset."""
        pass

    def get_dataset_spec(self, name: str, version: str | None = None) -> DatasetSpec:
        """
        Get a dataset spec by name and optional version.

        If version is not specified, resolves the best version using:
        1. "head" if it exists
        2. Highest semantic version
        3. Lexically last version
        """
        if version is None:
            versions = self.get_dataset_versions(name)
            version = resolve_version(versions)
        return self._get_dataset_spec(name, version)

    def download_dataset(
        self,
        name: str,
        version: str | None = None,
        overwrite: bool = False,
        output_dir: Path | None = None,
    ) -> list[DownloadedDatasetItem]:
        dataset = self.get_dataset_spec(name, version)

        paths = self._task_client.download_tasks(
            task_ids=[task_id.to_source_task_id() for task_id in dataset.tasks],
            overwrite=overwrite,
            output_dir=output_dir,
        )

        return [
            DownloadedDatasetItem(
                id=task_id,
                downloaded_path=path,
            )
            for task_id, path in zip(dataset.tasks, paths)
        ]
