"""Dataset registry for project-level data sources."""

import subprocess
from pathlib import Path

from goldfish.cloud.contracts import StorageURI
from goldfish.config import GoldfishConfig
from goldfish.db.database import Database
from goldfish.errors import GoldfishError, SourceAlreadyExistsError, SourceNotFoundError
from goldfish.models import SourceInfo
from goldfish.sources.conversion import source_row_to_info
from goldfish.validation import InvalidSourceMetadataError, validate_source_metadata


class DatasetRegistry:
    """Manage project-level datasets (immutable data sources)."""

    def __init__(self, db: Database, config: GoldfishConfig):
        """Initialize dataset registry.

        Args:
            db: Database instance
            config: Goldfish configuration
        """
        self.db = db
        self.config = config

    def register_dataset(
        self,
        name: str,
        source: str,
        description: str,
        format: str,
        metadata: dict,
        size_bytes: int | None = None,
    ) -> SourceInfo:
        """Register a project-level dataset.

        Args:
            name: Dataset identifier (e.g., "eurusd_raw_v3")
            source: Local path or storage URI (e.g., "local:/path/to/data.csv" or "s3://bucket/path")
            description: Human-readable description
            format: csv, npy, directory, etc.
            metadata: Required metadata dict
            size_bytes: Optional size in bytes

        Returns:
            SourceInfo for the registered dataset

        Raises:
            SourceAlreadyExistsError: If dataset with this name already exists
            GoldfishError: If GCS not configured (when source is local)
        """
        # Check if dataset already exists
        if self.db.source_exists(name):
            raise SourceAlreadyExistsError(f"Dataset '{name}' already exists")

        if metadata is None:
            raise InvalidSourceMetadataError("metadata is required for new datasets")

        validate_source_metadata(metadata)

        if description != metadata.get("description"):
            raise InvalidSourceMetadataError(
                f"description '{description}' does not match metadata.description '{metadata.get('description')}'"
            )

        source_format = metadata.get("source", {}).get("format")
        if format != source_format:
            raise InvalidSourceMetadataError(
                f"format '{format}' does not match metadata.source.format '{source_format}'"
            )

        metadata_size = metadata.get("source", {}).get("size_bytes")
        if metadata_size is None:
            raise InvalidSourceMetadataError("metadata.source.size_bytes is required for register_dataset")
        if size_bytes is not None and metadata_size != size_bytes:
            raise InvalidSourceMetadataError(
                f"size_bytes {size_bytes} does not match metadata.source.size_bytes {metadata_size}"
            )

        size_bytes = metadata_size

        # Parse source location
        if source.startswith("local:"):
            # Upload local file/directory to GCS
            local_path = Path(source[6:])
            if not local_path.exists():
                raise GoldfishError(f"Local source not found: {local_path}")

            gcs_location = self._upload_to_gcs(name, local_path)

        else:
            try:
                uri = StorageURI.parse(source)
            except ValueError as err:
                raise GoldfishError(
                    f"Invalid source format: {source}. Must start with 'local:' or be a valid storage URI"
                ) from err

            # Use storage URI directly
            gcs_location = str(uri)

        # Register in database
        self.db.create_source(
            source_id=name,
            name=name,
            gcs_location=gcs_location,
            created_by="external",
            description=description,
            size_bytes=size_bytes,
            status="available",
            metadata=metadata,
        )

        return self.get_dataset(name)

    def _upload_to_gcs(self, name: str, local_path: Path) -> str:
        """Upload local file/directory to GCS.

        Args:
            name: Dataset name (used as GCS path)
            local_path: Local file or directory path

        Returns:
            Storage URI for the uploaded object(s)

        Raises:
            GoldfishError: If GCS not configured or upload fails
        """
        if not self.config.gcs:
            raise GoldfishError(
                "GCS not configured. Cannot upload local datasets. Add GCS configuration to goldfish.yaml"
            )

        bucket = self.config.gcs.bucket
        prefix = (self.config.gcs.datasets_prefix or "datasets").rstrip("/")
        try:
            bucket_root = StorageURI.parse(bucket)
        except ValueError:
            bucket_root = StorageURI("gs", bucket, "")
        bucket_root = StorageURI(bucket_root.scheme, bucket_root.bucket, "")
        gcs_path = str(bucket_root.join(prefix, name))

        # Build gsutil command
        if local_path.is_dir():
            # Upload directory recursively
            cmd = ["gsutil", "-m", "cp", "-r", str(local_path), gcs_path]
        else:
            # Upload single file
            cmd = ["gsutil", "cp", str(local_path), gcs_path]

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                check=False,
            )

            if result.returncode != 0:
                error_msg = result.stderr.decode() if result.stderr else "Unknown error"
                raise GoldfishError(f"Failed to upload dataset to GCS: {error_msg}")

            return gcs_path

        except FileNotFoundError as err:
            raise GoldfishError(
                "gsutil command not found. Install Google Cloud SDK: https://cloud.google.com/sdk/docs/install"
            ) from err

    def list_datasets(self, status: str | None = None) -> list[SourceInfo]:
        """List all registered datasets.

        Args:
            status: Optional status filter (available, pending, failed)

        Returns:
            List of SourceInfo objects
        """
        sources = self.db.list_sources(status=status, created_by="external")
        return [source_row_to_info(source) for source in sources]

    def get_dataset(self, name: str) -> SourceInfo:
        """Get dataset details.

        Args:
            name: Dataset name

        Returns:
            SourceInfo object

        Raises:
            SourceNotFoundError: If dataset not found
        """
        source = self.db.get_source(name)
        if not source:
            raise SourceNotFoundError(f"Dataset not found: {name}")

        return source_row_to_info(source)

    def dataset_exists(self, name: str) -> bool:
        """Check if dataset exists.

        Args:
            name: Dataset name

        Returns:
            True if dataset exists, False otherwise
        """
        return self.db.source_exists(name)

    def delete_dataset(self, name: str) -> bool:
        """Delete a dataset.

        Args:
            name: Dataset name

        Returns:
            True if deleted, False if not found
        """
        return self.db.delete_source(name)
