from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any


class ScanType(str, Enum):
    TPI = "TPI"
    IMAGES = "Images"
    SPECTRUM = "Spectrum"
    ONOFF = "OnOff"


def normalize_scan_type(scan_type: ScanType | str) -> ScanType:
    if isinstance(scan_type, ScanType):
        return scan_type

    key = scan_type.strip().lower()
    aliases = {
        "tpi": ScanType.TPI,
        "image": ScanType.IMAGES,
        "images": ScanType.IMAGES,
        "spectrum": ScanType.SPECTRUM,
        "onoff": ScanType.ONOFF,
        "on_off": ScanType.ONOFF,
    }
    if key in aliases:
        return aliases[key]

    valid = ", ".join(item.value for item in ScanType)
    raise ValueError(f"Unknown scan type '{scan_type}'. Expected one of: {valid}")


def parse_datetime(value: Any) -> datetime | None:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value
    if isinstance(value, str):
        return datetime.fromisoformat(value.replace("Z", "+00:00"))
    raise TypeError(f"Expected datetime-compatible value, got: {type(value)!r}")


@dataclass
class ScanRecord:
    id: int
    scan_type: ScanType
    file_name: str
    file_path: str
    size: int
    source: str
    ra: float
    dec: float
    date: datetime
    date_obs: datetime | None
    date_end: datetime | None
    gain: int | None
    comment: str | None
    metadata: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_api(cls, scan_type: ScanType, payload: dict[str, Any]) -> "ScanRecord":
        try:
            scan_id = int(payload["id"])
            file_name = str(payload["file_name"])
            file_path = str(payload["file_path"])
            size = int(payload["size"])
            source = str(payload["object"])
            ra = float(payload["ra"])
            dec = float(payload["dec"])
            date = parse_datetime(payload["date"])
            if date is None:
                raise TypeError("date is required")
        except (KeyError, TypeError, ValueError) as exc:
            raise ValueError(f"Invalid scan payload: {payload}") from exc

        known_keys = {
            "id",
            "file_name",
            "file_path",
            "size",
            "object",
            "ra",
            "dec",
            "date",
            "date_obs",
            "date_end",
            "gain",
            "comment",
        }
        metadata = {k: v for k, v in payload.items() if k not in known_keys}

        return cls(
            id=scan_id,
            scan_type=scan_type,
            file_name=file_name,
            file_path=file_path,
            size=size,
            source=source,
            ra=ra,
            dec=dec,
            date=date,
            date_obs=parse_datetime(payload.get("date_obs")),
            date_end=parse_datetime(payload.get("date_end")),
            gain=payload.get("gain"),
            comment=payload.get("comment"),
            metadata=metadata,
        )
