"""Public draft capture and authenticated handoff into a project."""
from __future__ import annotations

import hashlib
import secrets
import shutil
from datetime import UTC, datetime, timedelta
from pathlib import Path
from uuid import UUID

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from pydantic import BaseModel, EmailStr, Field, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from src.config import get_settings
from src.core.database import get_db
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.codegen.credit_estimator import estimate_codegen_credits
from src.domain.guest_drafts.repository import GuestDraftRepository
from src.domain.projects.repository import ProjectRepository
from src.domain.prompt_media.paths import build_storage_relative_path
from src.domain.prompt_media.repository import PromptMediaRepository
from src.domain.prompt_media.validate import assert_allowed_mime, assert_file_size, kind_from_mime
from src.domain.workspaces.repository import MembershipRepository

router = APIRouter(prefix="/api/guest-drafts", tags=["guest-drafts"])


def _hash(token: str) -> str:
    return hashlib.sha256(token.encode()).hexdigest()


def _safe_filename(filename: str) -> str:
    return Path(filename).name.replace("\x00", "").strip() or "attachment"


class GuestDraftCreate(BaseModel):
    prompt: str = Field(min_length=1, max_length=100_000)
    # Optional: visitors who choose "log in" instead of signing up have no email to give.
    email: EmailStr | None = None

    @field_validator("email", mode="before")
    @classmethod
    def _blank_email_is_none(cls, value):
        if isinstance(value, str):
            value = value.strip()
            return value or None
        return value


class GuestDraftClaim(BaseModel):
    workspace_id: UUID


@router.post("")
async def create_guest_draft(body: GuestDraftCreate, db: AsyncSession = Depends(get_db)):
    token = secrets.token_urlsafe(32)
    draft = await GuestDraftRepository(db).create(
        token_hash=_hash(token),
        prompt=body.prompt.strip(),
        email=str(body.email).lower() if body.email else None,
        expires_at=datetime.now(UTC) + timedelta(days=90),
    )
    await db.commit()
    return {"token": token, "expires_at": draft.expires_at,
            "understanding": None}


@router.post("/{token}/assets")
async def upload_guest_asset(
    token: str,
    file: UploadFile = File(...),
    db: AsyncSession = Depends(get_db),
):
    repo = GuestDraftRepository(db)
    draft = await repo.get_active_by_hash(_hash(token))
    if not draft or draft.claimed_by_user_id:
        raise HTTPException(status_code=404, detail="Guest draft not found")
    content = await file.read()
    assert_file_size(len(content))
    filename = _safe_filename(file.filename or "attachment")
    mime_type = file.content_type or "application/octet-stream"
    assert_allowed_mime(mime_type, filename)
    kind = kind_from_mime(mime_type)
    if kind not in {"image", "file"}:
        raise HTTPException(status_code=400, detail="Only images and documents are supported")
    root = Path(get_settings().prompt_media_upload_root) / "guest-drafts" / str(draft.id)
    root.mkdir(parents=True, exist_ok=True)
    stored = f"{secrets.token_hex(8)}-{filename}"
    path = root / stored
    path.write_bytes(content)
    asset = await repo.add_asset(
        guest_draft_id=draft.id,
        original_filename=filename,
        mime_type=mime_type,
        byte_size=len(content),
        storage_relative_path=str(Path("guest-drafts") / str(draft.id) / stored),
        metadata_json={"kind": kind},
    )
    await db.commit()
    return {
        "id": asset.id,
        "original_filename": asset.original_filename,
        "byte_size": asset.byte_size,
    }


@router.post("/{token}/claim")
async def claim_guest_draft(
    token: str,
    body: GuestDraftClaim,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    repo = GuestDraftRepository(db)
    draft = await repo.get_active_by_hash(_hash(token))
    if not draft or (draft.claimed_by_user_id and draft.claimed_by_user_id != user.id):
        raise HTTPException(status_code=404, detail="Guest draft not found")
    has_permission = await MembershipRepository(db).has_permission(
        body.workspace_id,
        user.id,
        "projects.write",
    )
    if not has_permission:
        raise HTTPException(status_code=403, detail="Workspace access is required")
    draft.claimed_by_user_id = user.id
    draft.claimed_at = datetime.now(UTC)
    assets = await repo.list_assets(draft.id)
    await db.commit()
    return {
        "id": draft.id,
        "prompt": draft.prompt,
        "workspace_id": body.workspace_id,
        "assets": [
            {"id": asset.id, "name": asset.original_filename, "byte_size": asset.byte_size}
            for asset in assets
        ],
    }


class AttachGuestDraft(BaseModel):
    project_id: UUID


@router.post("/{draft_id}/estimate")
async def estimate_guest_draft(
    draft_id: UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    repo = GuestDraftRepository(db)
    draft = await repo.get_active_by_id(draft_id)
    if not draft or draft.claimed_by_user_id != user.id:
        raise HTTPException(status_code=404, detail="Guest draft not found")

    assets = await repo.list_assets(draft.id)
    estimate = estimate_codegen_credits(
        prompt=draft.prompt,
        attachment_sizes=[asset.byte_size for asset in assets],
        attachment_count=len(assets),
    )
    return {
        "minimum_credits": estimate.minimum_credits,
        "maximum_credits": estimate.maximum_credits,
        "prompt_credits": estimate.prompt_credits,
        "attachment_credits": estimate.attachment_credits,
    }


@router.post("/{draft_id}/attach")
async def attach_guest_draft(
    draft_id: UUID,
    body: AttachGuestDraft,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    repo = GuestDraftRepository(db)
    draft = await repo.get_active_by_id(draft_id)
    project = await ProjectRepository(db).get_by_id(body.project_id)
    if not draft or draft.claimed_by_user_id != user.id or not project:
        raise HTTPException(status_code=404, detail="Guest draft or project not found")
    has_permission = await MembershipRepository(db).has_permission(
        project.workspace_id,
        user.id,
        "projects.write",
    )
    if not has_permission:
        raise HTTPException(status_code=403, detail="Project access is required")
    assets = await repo.list_assets(draft.id)
    media = PromptMediaRepository(db)
    root = Path(get_settings().prompt_media_upload_root)
    attachment_ids = []
    for asset in assets:
        source = root / asset.storage_relative_path
        if not source.exists():
            continue
        relative = build_storage_relative_path(
            project.workspace_id,
            project.id,
            asset.original_filename,
        )
        target = root / relative
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copyfile(source, target)
        row = await media.create(
            project_id=project.id,
            uploaded_by=user.id,
            kind=(asset.metadata_json or {}).get("kind", "file"),
            storage_relative_path=relative,
            original_filename=asset.original_filename,
            mime_type=asset.mime_type,
            byte_size=asset.byte_size,
        )
        attachment_ids.append(row.id)
    discovery = project.discovery_json
    if discovery and discovery.get("status") == "pending":
        project.discovery_json = {**discovery, "initial_prompt": draft.prompt,
                                  "attachment_ids": list(map(str, attachment_ids))}
    await db.commit()
    return {"prompt": draft.prompt, "attachment_ids": attachment_ids,
            "discovery_required": bool(discovery and discovery.get("status") != "completed")}
