from __future__ import annotations

import re
from datetime import datetime
from typing import Any, Dict, Optional
from uuid import UUID

from pydantic import BaseModel, EmailStr, Field, field_validator, ConfigDict

MAX_EVENT_NAME = 80
MAX_EXTERNAL_USER_ID = 128
MAX_IDEMPOTENCY_KEY = 80


# ============================
# STORAGE
# ============================

def _normalize_folder_path(v: str) -> str:
    """Normalize and validate folder paths.

    Mirrors backend behaviour:
    - trim whitespace + leading/trailing slashes
    - convert whitespace runs to a single underscore
    - reject empty paths
    - reject '.' to prevent traversal-like paths
    """
    v = (v or "").strip().strip("/")
    v = re.sub(r"\s+", "_", v)

    if not v:
        raise ValueError("Folder path cannot be empty")
    if "." in v:
        raise ValueError("Invalid folder path")
    return v


class StorageUploadRequest(BaseModel):
    filename: str
    content_type: str
    size_bytes: int
    is_public: bool = False
    folder_id: Optional[UUID] = None
    generate_thumbnail: bool = False
    transcode_preset: str = Field(default="none", max_length=20)
    
    @field_validator("size_bytes")
    @classmethod
    def validate_size_bytes(cls, v: int) -> int:
        iv = int(v)
        if iv <= 0:
            raise ValueError("File size must be positive")
        return iv
    
    @field_validator("transcode_preset")
    @classmethod
    def validate_transcode_preset(cls, v: str) -> str:
        v = (v or "none").strip().lower()
        if v not in {"none", "normalize"}:
            raise ValueError("Invalid transcode_preset")
        return v


class StorageConfirmRequest(BaseModel):
    object_id: UUID


class StorageDownloadRequest(BaseModel):
    object_id: UUID


class StoragePrivacyRequest(BaseModel):
    is_public: bool


class FolderCreateRequest(BaseModel):
    path: str

    @field_validator("path")
    @classmethod
    def validate_path(cls, v: str) -> str:
        return _normalize_folder_path(v)


# ----------------------------
# Responses
# ----------------------------

class StorageUploadResponse(BaseModel):
    uploadUrl: str
    fileUrl: str
    object_id: UUID


class StorageDownloadResponse(BaseModel):
    downloadUrl: str
    expiresIn: int


class StorageObjectOut(BaseModel):
    id: UUID
    path: str
    size_bytes: Optional[int] = None
    content_type: Optional[str] = None
    is_public: bool
    created_at: datetime
    folder_id: Optional[UUID] = None
    status: str
    expires_at: Optional[datetime] = None
    thumbnail_object_id: Optional[UUID] = None
    transcode_preset: str = "none"
    transcode_status: str = "none"
    transcode_failure_reason: Optional[str] = None
    normalized_object_id: Optional[UUID] = None
    model_config = ConfigDict(from_attributes=True)



class StorageDeleteResponse(BaseModel):
    detail: str


class FolderOut(BaseModel):
    id: UUID
    path: str
    created_at: datetime

    class Config:
        from_attributes = True


class FolderDeleteResponse(BaseModel):
    detail: str


# ============================
# STREAMING / CDN
# ============================

class StreamUrlResponse(BaseModel):
    url: str
    expires_in: int


# ============================
# ATHENA
# ============================

class AthenaLogicEventIngest(BaseModel):
    external_user_id: str
    event_name: str
    occurred_at: datetime
    properties: Optional[Dict[str, Any]] = None
    idempotency_key: Optional[str] = Field(default=None, max_length=255)


class AthenaIngestResponse(BaseModel):
    ok: bool
    deduplicated: Optional[bool] = None


# ============================
# MESSAGING (API key)
# ============================

class EventIngestRequest(BaseModel):
    event: str = Field(..., min_length=1, max_length=MAX_EVENT_NAME)
    external_user_id: Optional[str] = Field(None, min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    data: Optional[Dict[str, Any]] = None
    idempotency_key: Optional[str] = Field(None, max_length=MAX_IDEMPOTENCY_KEY)


class EventIngestResponse(BaseModel):
    event_id: UUID
    status: str
    deliveries_planned: int


class RegisterDeviceIdentityRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    token: str = Field(..., min_length=10, max_length=512)
    platform: str = Field(..., min_length=2, max_length=16)
    app_id: Optional[str] = Field(None, max_length=64)


class DeviceIdentityResponse(BaseModel):
    id: UUID
    external_user_id: str
    platform: str
    app_id: Optional[str]
    is_active: bool
    created_at: datetime
    last_seen_at: Optional[datetime] = None


class UnregisterDeviceIdentityRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    token: str = Field(..., min_length=10, max_length=512)


class UnregisterDeviceIdentityResponse(BaseModel):
    ok: bool


class RegisterEmailIdentityRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    email: str = Field(..., min_length=3, max_length=320)
    label: Optional[str] = Field(None, max_length=64)


class EmailIdentityResponse(BaseModel):
    id: UUID
    external_user_id: str
    email: str
    label: Optional[str]
    verification_level: str    # "none" | "user_verified" | "provider_verified" | "workspace_verified"
    is_active: bool
    created_at: datetime
    last_seen_at: Optional[datetime]


class StartEmailVerifyRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    email: EmailStr
    label: Optional[str] = Field(None, max_length=64)


class StartEmailVerifyResponse(BaseModel):
    ok: bool


class ConfirmEmailVerifyRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    email: EmailStr
    code: str = Field(..., min_length=4, max_length=12)


class ConfirmEmailVerifyResponse(BaseModel):
    ok: bool


class WebTokenRegisterRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    token: str = Field(..., min_length=1, max_length=2048)
    app_id: Optional[str] = Field(None, max_length=64)


class WebTokenUnregisterRequest(BaseModel):
    external_user_id: str = Field(..., min_length=1, max_length=MAX_EXTERNAL_USER_ID)
    token: str = Field(..., min_length=1, max_length=2048)


class DirectSendRequest(BaseModel):
    channel: str
    external_user_id: str
    title: Optional[str] = None
    body: Optional[str] = None
    data: Optional[Dict[str, Any]] = None
    priority: str = "standard"
    idempotency_key: Optional[str] = None


class DirectSendResponse(BaseModel):
    ok: bool
    deliveries_created: int
    
class BulkEmailImportRequest(BaseModel):
    users: list[dict]
    

class GeocodeResult(BaseModel):
    id: str
    label: str
    lat: float
    lng: float
    type: str = "unknown"     
    source: str = "lamen-osm"