import json
from typing import Any
from urllib.parse import parse_qs

from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from app.config import settings
from app.db import get_session
from app.models.entities import LineEventRecord, Room
from app.services import participants as participants_svc
from app.services.line_messaging import reply_join_link
from app.services.line_verify import verify_line_signature

router = APIRouter(prefix="/webhook", tags=["line"])


def _event_id(event: dict[str, Any]) -> str:
    wid = event.get("webhookEventId")
    if isinstance(wid, str) and wid:
        return wid
    # fallback: stable-ish synthetic id for older payloads
    return hashlib_fallback(event)


def hashlib_fallback(event: dict[str, Any]) -> str:
    import hashlib

    raw = json.dumps(event, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()


async def _resolve_room_id(session: AsyncSession, event: dict[str, Any]) -> str:
    postback_data = str((event.get("postback") or {}).get("data") or "").strip()
    if postback_data:
        parsed = parse_qs(postback_data, keep_blank_values=True)
        room_from_postback = (parsed.get("room_id") or [""])[0].strip()
        if room_from_postback:
            return room_from_postback

    text = str((event.get("message") or {}).get("text") or "").strip()
    if text:
        room = await session.get(Room, text)
        if room:
            return room.id
        if text.startswith("room:"):
            return text.split(":", 1)[1].strip()

    return settings.line_default_room_id.strip()


def _build_join_url(request: Request, token: str) -> str:
    base = (settings.app_base_url or "").strip().rstrip("/")
    if not base:
        base = str(request.base_url).rstrip("/")
    return f"{base}/join?token={token}"


def _event_supports_join_reply(event: dict[str, Any]) -> bool:
    return str(event.get("type") or "") in {"follow", "message", "postback"}


@router.post("/line")
async def line_webhook(
    request: Request,
    session: AsyncSession = Depends(get_session),
):
    if not settings.line_channel_secret:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="LINE_CHANNEL_SECRET not configured",
        )

    body = await request.body()
    sig = request.headers.get("X-Line-Signature")
    if not verify_line_signature(body, sig, settings.line_channel_secret):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid signature")

    try:
        payload = json.loads(body.decode("utf-8"))
    except json.JSONDecodeError:
        raise HTTPException(status_code=400, detail="invalid json")

    events = payload.get("events") or []
    if not isinstance(events, list):
        events = []

    for event in events:
        if not isinstance(event, dict):
            continue
        eid = _event_id(event)
        existing = await session.exec(select(LineEventRecord).where(LineEventRecord.event_id == eid))
        if existing.first():
            continue
        event_type = str(event.get("type", ""))
        raw_json = json.dumps(event, ensure_ascii=False)
        preview = json.dumps(event, ensure_ascii=False)[:500]
        room_id = ""
        participant_id = ""
        line_user_id_hash = ""
        reply_status = "not_attempted"
        reply_error = ""

        source = event.get("source") or {}
        line_user_id = str(source.get("userId") or "").strip()
        if _event_supports_join_reply(event):
            room_id = await _resolve_room_id(session, event)
            if room_id:
                participant = await participants_svc.get_or_create_participant(
                    session,
                    room_id=room_id,
                    line_user_id=line_user_id,
                    source=f"line_{event_type}",
                )
                participant_id = participant.id
                line_user_id_hash = participant.line_user_id_hash
                join_token = await participants_svc.issue_join_token(
                    session,
                    participant=participant,
                    event_id=eid,
                    source="line_webhook_reply",
                    metadata={
                        "event_type": event_type,
                        "reply_token_present": bool(event.get("replyToken")),
                    },
                )
                join_url = _build_join_url(request, join_token.token)
                reply_token = str(event.get("replyToken") or "").strip()
                if reply_token:
                    result = await reply_join_link(
                        reply_token=reply_token,
                        join_url=join_url,
                        room_id=room_id,
                    )
                    reply_status = "ok" if result.get("ok") else "failed"
                    reply_error = "" if result.get("ok") else json.dumps(result, ensure_ascii=False)
                else:
                    reply_status = "skipped_no_reply_token"
            else:
                reply_status = "skipped_no_room_id"

        session.add(
            LineEventRecord(
                event_id=eid,
                event_type=event_type,
                raw_preview=preview,
                raw_json=raw_json,
                room_id=room_id,
                participant_id=participant_id,
                line_user_id_hash=line_user_id_hash,
                reply_status=reply_status,
                reply_error=reply_error[:500],
            )
        )

    await session.commit()
    return {"status": "ok"}
