"""Share-token endpoints — create/revoke/list tokens + public read-only view + client decision."""

import secrets
from datetime import datetime, timedelta
from typing import Literal

from fastapi import APIRouter, Depends, HTTPException, Request
from motor.motor_asyncio import AsyncIOMotorDatabase
from pydantic import BaseModel

from ...core.config import settings
from ...core.database import get_database
from ...core.dependencies import require_roles
from ...models.audit_log import AuditAction
from ...models.share_token import ShareTokenResponse
from ...models.user import User, UserRole
from ...services.audit_logger import audit_logger
from ...services.gcs import get_signed_download_url

router = APIRouter(tags=["share"])

_TOKENS = "share_tokens"
_JOBS = "jobs"


def _share_url(token: str) -> str:
    return f"{settings.app_url}/share/{token}"


# ── Request schemas ───────────────────────────────────────────────────────────

class CreateShareTokenRequest(BaseModel):
    expires_in_days: int | None = 30  # None = no expiry
    label: str | None = None


class ShareTokenListResponse(BaseModel):
    tokens: list[ShareTokenResponse]


class PublicJobPreviewLanguage(BaseModel):
    captions_vtt_url: str | None = None
    audio_description_vtt_url: str | None = None
    accessible_video_mp4_url: str | None = None
    audio_description_mp3_url: str | None = None


class PublicJobPreviewResponse(BaseModel):
    job_id: str
    job_title: str
    job_status: str
    source_language: str
    languages: list[str]
    language_outputs: dict[str, PublicJobPreviewLanguage]


class ClientDecisionRequest(BaseModel):
    action: Literal["approve", "reject"]
    notes: str | None = None
    client_name: str | None = None


class ClientDecisionResponse(BaseModel):
    status: str
    new_job_status: str


# ── Authenticated routes ──────────────────────────────────────────────────────

@router.post("/jobs/{job_id}/share", response_model=ShareTokenResponse, status_code=201)
async def create_share_token(
    job_id: str,
    request: CreateShareTokenRequest,
    http_request: Request,
    current_user: User = Depends(require_roles(
        UserRole.PROJECT_MANAGER, UserRole.PRODUCTION, UserRole.ADMIN,
    )),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    """Generate a read-only share link for a job."""
    job_doc = await db[_JOBS].find_one({"_id": job_id})
    if not job_doc:
        raise HTTPException(status_code=404, detail="Job not found")

    token_id = secrets.token_hex(32)
    now = datetime.utcnow()
    expires_at = (now + timedelta(days=request.expires_in_days)) if request.expires_in_days else None

    token_doc = {
        "_id": token_id,
        "job_id": job_id,
        "organization_id": job_doc.get("organization_id", ""),
        "created_by_user_id": str(current_user.id),
        "created_by_email": current_user.email,
        "created_at": now,
        "expires_at": expires_at,
        "is_active": True,
        "label": request.label,
    }
    await db[_TOKENS].insert_one(token_doc)
    await audit_logger.log_action(
        action=AuditAction.SHARE_TOKEN_CREATE,
        description=f"Share token created for job '{job_id}'",
        user=current_user,
        request=http_request,
        resource_type="job",
        resource_id=job_id,
        details={"token_id": token_id, "label": request.label, "expires_in_days": request.expires_in_days},
    )

    return ShareTokenResponse(
        id=token_id,
        job_id=job_id,
        created_by_email=current_user.email,
        created_at=now,
        expires_at=expires_at,
        is_active=True,
        label=request.label,
        share_url=_share_url(token_id),
    )


@router.get("/jobs/{job_id}/share", response_model=ShareTokenListResponse)
async def list_share_tokens(
    job_id: str,
    current_user: User = Depends(require_roles(
        UserRole.PROJECT_MANAGER, UserRole.PRODUCTION, UserRole.ADMIN,
    )),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    """List all active share tokens for a job."""
    job_doc = await db[_JOBS].find_one({"_id": job_id})
    if not job_doc:
        raise HTTPException(status_code=404, detail="Job not found")

    cursor = db[_TOKENS].find({"job_id": job_id, "is_active": True})
    tokens = []
    async for doc in cursor:
        tokens.append(ShareTokenResponse(
            id=doc["_id"],
            job_id=doc["job_id"],
            created_by_email=doc["created_by_email"],
            created_at=doc["created_at"],
            expires_at=doc.get("expires_at"),
            is_active=doc["is_active"],
            label=doc.get("label"),
            share_url=_share_url(doc["_id"]),
        ))
    return ShareTokenListResponse(tokens=tokens)


@router.delete("/jobs/{job_id}/share/{token_id}", status_code=204)
async def revoke_share_token(
    job_id: str,
    token_id: str,
    http_request: Request,
    current_user: User = Depends(require_roles(
        UserRole.PROJECT_MANAGER, UserRole.PRODUCTION, UserRole.ADMIN,
    )),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    """Revoke (deactivate) a share token."""
    result = await db[_TOKENS].update_one(
        {"_id": token_id, "job_id": job_id},
        {"$set": {"is_active": False}},
    )
    if result.matched_count == 0:
        raise HTTPException(status_code=404, detail="Token not found")
    await audit_logger.log_action(
        action=AuditAction.SHARE_TOKEN_REVOKE,
        description=f"Share token '{token_id}' revoked for job '{job_id}'",
        user=current_user,
        request=http_request,
        resource_type="job",
        resource_id=job_id,
        details={"token_id": token_id},
    )


# ── Public route (no auth) ────────────────────────────────────────────────────

@router.get("/public/share/{token}", response_model=PublicJobPreviewResponse)
async def get_public_job_preview(
    token: str,
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    """Return read-only job preview for a valid share token. No authentication required."""
    token_doc = await db[_TOKENS].find_one({"_id": token, "is_active": True})
    if not token_doc:
        raise HTTPException(status_code=404, detail="Share link not found or has been revoked")

    if token_doc.get("expires_at") and token_doc["expires_at"] < datetime.utcnow():
        raise HTTPException(status_code=410, detail="Share link has expired")

    job_doc = await db[_JOBS].find_one({"_id": token_doc["job_id"]})
    if not job_doc:
        raise HTTPException(status_code=404, detail="Job not found")

    outputs = job_doc.get("outputs") or {}
    language_outputs: dict[str, PublicJobPreviewLanguage] = {}

    for lang, lang_output in outputs.items():
        if not isinstance(lang_output, dict):
            continue

        lang_data = PublicJobPreviewLanguage()

        if "captions_vtt_gcs" in lang_output:
            blob_path = lang_output["captions_vtt_gcs"].replace(f"gs://{settings.gcs_bucket}/", "")
            try:
                lang_data.captions_vtt_url = await get_signed_download_url(blob_path, 6)
            except Exception:
                pass

        if "ad_vtt_gcs" in lang_output:
            blob_path = lang_output["ad_vtt_gcs"].replace(f"gs://{settings.gcs_bucket}/", "")
            try:
                lang_data.audio_description_vtt_url = await get_signed_download_url(blob_path, 6)
            except Exception:
                pass

        if "ad_mp3_gcs" in lang_output:
            blob_path = lang_output["ad_mp3_gcs"].replace(f"gs://{settings.gcs_bucket}/", "")
            try:
                lang_data.audio_description_mp3_url = await get_signed_download_url(blob_path, 6)
            except Exception:
                pass

        if "accessible_video_gcs" in lang_output:
            blob_path = lang_output["accessible_video_gcs"].replace(f"gs://{settings.gcs_bucket}/", "")
            try:
                lang_data.accessible_video_mp4_url = await get_signed_download_url(blob_path, 6)
            except Exception:
                pass

        language_outputs[lang] = lang_data

    return PublicJobPreviewResponse(
        job_id=str(job_doc["_id"]),
        job_title=job_doc.get("title", "Untitled"),
        job_status=job_doc.get("status", ""),
        source_language=job_doc.get("source", {}).get("language", "en"),
        languages=list(outputs.keys()),
        language_outputs=language_outputs,
    )


@router.post("/public/share/{token}/decision", response_model=ClientDecisionResponse)
async def client_decision(
    token: str,
    request: ClientDecisionRequest,
    http_request: Request,
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    """Submit client approval or rejection via a share link. No authentication required."""
    from ...services.validation import asset_validation_service

    token_doc = await db[_TOKENS].find_one({"_id": token, "is_active": True})
    if not token_doc:
        raise HTTPException(status_code=404, detail="Share link not found or has been revoked")
    if token_doc.get("expires_at") and token_doc["expires_at"] < datetime.utcnow():
        raise HTTPException(status_code=410, detail="Share link has expired")

    job_id = token_doc["job_id"]
    job_doc = await db[_JOBS].find_one({"_id": job_id})
    if not job_doc:
        raise HTTPException(status_code=404, detail="Job not found")

    if job_doc.get("status") != "pending_final_review":
        raise HTTPException(
            status_code=409,
            detail="This job is not currently awaiting client review"
        )

    now = datetime.utcnow()
    by_label = f"client:{request.client_name or 'anonymous'} (share/{token[:8]})"

    if request.action == "approve":
        is_valid, validation_errors = await asset_validation_service.validate_job_assets(job_doc)
        if not is_valid:
            raise HTTPException(
                status_code=400,
                detail=f"Asset validation failed: {'; '.join(validation_errors)}"
            )
        new_status = "completed"
        update = {
            "$set": {
                "status": new_status,
                "review.notes": request.notes or "",
                "updated_at": now,
            },
            "$push": {
                "review.history": {
                    "at": now,
                    "status": new_status,
                    "by": by_label,
                    "notes": request.notes or "",
                }
            },
        }
    else:
        new_status = "qc_feedback"
        update = {
            "$set": {
                "status": new_status,
                "review.notes": request.notes or "",
                "review.reviewer_id": by_label,
                "updated_at": now,
            },
            "$push": {
                "review.history": {
                    "at": now,
                    "status": new_status,
                    "by": by_label,
                    "notes": request.notes or "",
                }
            },
        }

    result = await db[_JOBS].find_one_and_update(
        {"_id": job_id, "status": "pending_final_review"},
        update,
        return_document=True,
    )
    if not result:
        raise HTTPException(
            status_code=409,
            detail="Decision could not be submitted — the job status may have changed"
        )

    await audit_logger.log_action(
        action=AuditAction.SHARE_CLIENT_DECISION,
        description=f"Client '{request.client_name or 'anonymous'}' submitted decision '{request.action}' for job '{job_id}' via share token",
        user=None,
        request=http_request,
        resource_type="job",
        resource_id=job_id,
        details={
            "action": request.action,
            "token": token,
            "client_name": request.client_name,
            "new_status": new_status,
            "notes": request.notes,
        },
    )

    if request.action == "approve":
        try:
            from ...tasks.notify import notify_client_task
            notify_client_task.delay(job_id)
        except Exception:
            pass

    return ClientDecisionResponse(status="ok", new_job_status=new_status)
