from __future__ import annotations

import enum
import uuid
from datetime import datetime, timedelta, timezone
from typing import Dict, List, Literal, Optional, Union

from pydantic import BaseModel, Field, HttpUrl, field_validator


def _expiry_default() -> datetime:
    return datetime.now(tz=timezone.utc) + timedelta(days=7)


class SourceMode(str, enum.Enum):
    upload = "upload"
    s3_pull = "s3_pull"


class UploadSource(BaseModel):
    mode: Literal["upload"]
    filename: str = Field(..., description="Customer supplied filename for auditing")


class S3Source(BaseModel):
    mode: Literal["s3_pull"]
    bucket: str
    key: str
    region: Optional[str] = None


Source = Union[UploadSource, S3Source]


class JobConfig(BaseModel):
    lock_cols: List[str] = Field(default_factory=list)
    residual_cols: List[str] = Field(default_factory=list)
    k: int = Field(default=64, ge=2, le=1024)
    uncertainty_threshold: float = Field(default=0.2, ge=0.0, le=1.0)

    @field_validator("lock_cols", "residual_cols", mode="after")
    def _dedupe_cols(cls, value: List[str]) -> List[str]:
        seen: Dict[str, None] = {}
        for col in value:
            key = col.strip()
            if key:
                seen[key] = None
        return list(seen.keys())


class JobStatus(str, enum.Enum):
    pending = "pending"
    uploaded = "uploaded"
    processing = "processing"
    succeeded = "succeeded"
    failed = "failed"
    cancelled = "cancelled"


class JobCreateRequest(BaseModel):
    source: Source
    config: JobConfig
    webhook_url: Optional[HttpUrl] = Field(None, description="Optional webhook to notify on status changes")
    metadata: Dict[str, str] = Field(default_factory=dict, description="Arbitrary customer metadata (key/value)")


class JobCreateResponse(BaseModel):
    job_id: str
    status: JobStatus
    upload_url: Optional[HttpUrl]
    expires_at: datetime


class ArtifactLink(BaseModel):
    type: Literal["smp", "reconstruction", "report", "log"]
    url: HttpUrl
    expires_at: datetime


class ArtifactMeta(BaseModel):
    path: str
    created_at: datetime


class JobSummary(BaseModel):
    job_id: str
    status: JobStatus
    created_at: datetime
    updated_at: datetime
    compression_ratio: Optional[float] = None
    metrics: Dict[str, float] = Field(default_factory=dict)
    artifacts: List[ArtifactLink] = Field(default_factory=list)
    uncertain_cells: Dict[str, List[int]] = Field(default_factory=dict)
    error_message: Optional[str] = None
    expires_at: datetime = Field(default_factory=_expiry_default)
    artifact_meta: Dict[str, ArtifactMeta] = Field(default_factory=dict)
    artifact_sizes: Dict[str, int] = Field(default_factory=dict)
    original_size_bytes: Optional[int] = None


class UsageWindow(BaseModel):
    start: datetime
    end: datetime
    compressed_gb: float
    jobs: int
    quota_gb: Optional[float] = None


class UsageResponse(BaseModel):
    windows: List[UsageWindow]
    next_cursor: Optional[str] = None


def new_job_id() -> str:
    return f"job_{uuid.uuid4().hex}"
