import re
import uuid
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeout
from datetime import datetime, timezone
from pathlib import Path

from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import Response
from sqlalchemy.orm import Session, joinedload

from app.core.config import settings
from app.core.database import SessionLocal, get_db
from app.core.deps import get_current_user
from app.models.user import Composition, CompositionInputMode, CompositionStatus, SystemSettings, User
from app.schemas import CompositionCreate, CompositionInputModeEnum, CompositionOcrOut, CompositionOut
from app.services import llm as llm_service
from app.services import ocr as ocr_service
from app.services.student_access import get_student_for_user

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


def _ocr_service_url(db: Session) -> str | None:
    row = db.get(SystemSettings, 1)
    if row and row.ocr_service_url:
        return row.ocr_service_url.strip() or None
    return ocr_service.resolve_ocr_service_url()


def _to_out(item: Composition) -> CompositionOut:
    return CompositionOut(
        id=item.id,
        student_id=item.student_id,
        topic=item.topic,
        input_mode=CompositionInputModeEnum(item.input_mode.value),
        writing_plan=item.writing_plan,
        sample_essay=item.sample_essay,
        error_message=item.error_message,
        status=item.status,
        created_at=item.created_at,
        updated_at=item.updated_at,
    )


def _safe_filename(name: str) -> str:
    cleaned = re.sub(r'[\\/:*?"<>|]', "_", name).strip() or "composition"
    return cleaned[:40]


async def _generate_composition(composition_id: uuid.UUID):
    db = SessionLocal()
    try:
        item = (
            db.query(Composition)
            .options(joinedload(Composition.student))
            .filter(Composition.id == composition_id)
            .first()
        )
        if item is None:
            return
        student = item.student
        item.status = CompositionStatus.generating
        item.error_message = None
        db.commit()

        ai_cfg = llm_service.load_ai_config(db)
        try:
            plan, essay = await llm_service.generate_composition(
                ai_cfg,
                item.topic,
                student.school_level if student else None,
                student.grade if student else None,
            )
            item.writing_plan = plan or None
            item.sample_essay = essay or None
            if not item.writing_plan and not item.sample_essay:
                raise ValueError("AI 未返回有效内容")
            item.status = CompositionStatus.done
            item.error_message = None
        except Exception as exc:
            item.status = CompositionStatus.failed
            item.error_message = str(exc)[:500]
        item.updated_at = datetime.now(timezone.utc)
        db.commit()
    finally:
        db.close()


@router.get("/students/{student_id}/compositions", response_model=list[CompositionOut])
def list_compositions(
    student_id: uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    get_student_for_user(db, student_id, current_user.id)
    items = (
        db.query(Composition)
        .filter(Composition.student_id == student_id)
        .order_by(Composition.created_at.desc())
        .all()
    )
    return [_to_out(item) for item in items]


@router.post(
    "/students/{student_id}/compositions",
    response_model=CompositionOut,
    status_code=status.HTTP_201_CREATED,
)
async def create_composition(
    student_id: uuid.UUID,
    data: CompositionCreate,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    get_student_for_user(db, student_id, current_user.id)
    item = Composition(
        student_id=student_id,
        topic=data.topic.strip(),
        input_mode=CompositionInputMode(data.input_mode.value),
        status=CompositionStatus.pending,
    )
    db.add(item)
    db.commit()
    db.refresh(item)
    background_tasks.add_task(_generate_composition, item.id)
    return _to_out(item)


@router.post("/students/{student_id}/compositions/ocr", response_model=CompositionOcrOut)
async def ocr_composition_topic(
    student_id: uuid.UUID,
    file: UploadFile = File(...),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    get_student_for_user(db, student_id, current_user.id)
    content = await file.read()
    if len(content) > settings.MAX_UPLOAD_SIZE:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="文件超过10MB限制")

    tmp_dir = Path(settings.UPLOAD_DIR) / "ocr-tmp"
    tmp_dir.mkdir(parents=True, exist_ok=True)
    tmp_path = tmp_dir / f"{uuid.uuid4()}.jpg"
    tmp_path.write_bytes(content)
    ocr_url = _ocr_service_url(db)
    try:
        with ThreadPoolExecutor(max_workers=1) as pool:
            future = pool.submit(ocr_service.run_ocr_with_regions, str(tmp_path), ocr_url)
            result = future.result(timeout=settings.OCR_TIMEOUT_SECONDS)
        text = (result.get("text") or "").strip()
        if not text:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="OCR 未识别到文字")
        return CompositionOcrOut(text=text)
    except FuturesTimeout:
        raise HTTPException(
            status_code=status.HTTP_504_GATEWAY_TIMEOUT,
            detail=f"OCR 识别超时（>{settings.OCR_TIMEOUT_SECONDS}秒）",
        ) from None
    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"OCR 识别失败：{exc}",
        ) from exc
    finally:
        tmp_path.unlink(missing_ok=True)


@router.get("/compositions/{composition_id}", response_model=CompositionOut)
def get_composition(
    composition_id: uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    item = (
        db.query(Composition)
        .join(Composition.student)
        .filter(Composition.id == composition_id)
        .first()
    )
    if item is None or item.student.user_id != current_user.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="作文记录不存在")
    return _to_out(item)


@router.post("/compositions/{composition_id}/regenerate", response_model=CompositionOut)
async def regenerate_composition(
    composition_id: uuid.UUID,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    item = (
        db.query(Composition)
        .join(Composition.student)
        .filter(Composition.id == composition_id)
        .first()
    )
    if item is None or item.student.user_id != current_user.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="作文记录不存在")

    item.status = CompositionStatus.pending
    item.error_message = None
    item.writing_plan = None
    item.sample_essay = None
    db.commit()
    background_tasks.add_task(_generate_composition, item.id)
    db.refresh(item)
    return _to_out(item)


@router.get("/compositions/{composition_id}/download")
def download_composition(
    composition_id: uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    item = (
        db.query(Composition)
        .join(Composition.student)
        .filter(Composition.id == composition_id)
        .first()
    )
    if item is None or item.student.user_id != current_user.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="作文记录不存在")
    if item.status != CompositionStatus.done:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="作文尚未生成完成")

    body = llm_service.composition_markdown(item.topic, item.writing_plan, item.sample_essay)
    filename = f"{_safe_filename(item.topic)}.md"
    from urllib.parse import quote

    encoded = quote(filename)
    return Response(
        content=body.encode("utf-8"),
        media_type="text/markdown; charset=utf-8",
        headers={
            "Content-Disposition": f'attachment; filename="composition.md"; filename*=UTF-8\'\'{encoded}'
        },
    )


@router.delete("/compositions/{composition_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_composition(
    composition_id: uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    item = (
        db.query(Composition)
        .join(Composition.student)
        .filter(Composition.id == composition_id)
        .first()
    )
    if item is None or item.student.user_id != current_user.id:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="作文记录不存在")
    db.delete(item)
    db.commit()
