"""events API — 개인 운영 로그 / 일정 / 할 일 / 회고 (PR-1).

PR-1 scope (plan beszel-tingly-sloth.md v6):
- POST /api/events (kind=task/calendar_event/activity_log)
- GET /api/events/{id}
- GET /api/events?kind&status&from&to&project_tag&source
- PATCH /api/events/{id}  (허용 필드만, 시간 필드 변경 시 reschedule history)
- POST /api/events/{id}/complete | /cancel | /defer | /reactivate
- GET /api/events/today  (timezone 정책 적용)
- GET /api/events/inbox
- GET /api/events/activity?from&to

PR-1 제외: DELETE / log shortcut / upcoming / ingest / iCal / ntfy.
"""

import json
import logging
from datetime import date, datetime, timedelta, timezone
from typing import Annotated, Any
from zoneinfo import ZoneInfo

from fastapi import APIRouter, Body, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy import and_, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user
from core.database import get_session
from models.event import Event
from models.event_history import EventHistory
from models.user import User

logger = logging.getLogger(__name__)

router = APIRouter()

DEFAULT_TIMEZONE = "Asia/Seoul"

# PATCH 허용 필드 — status/completed_at/cancelled_at/defer_until/source/source_ref/
# raw_metadata/user_id/created_by 는 lifecycle endpoint 또는 시스템 결정.
PATCH_ALLOWED_FIELDS = {
    "title",
    "description",
    "due_at",
    "start_at",
    "end_at",
    "started_at",
    "ended_at",
    "all_day",
    "timezone",
    "priority",
    "project_tag",
    "tags",
    "memo_document_id",
}
# 시간 필드 변경 시 reschedule history 1건 자동 기록 (defer_until 은 /defer 전용).
RESCHEDULE_TIME_FIELDS = {
    "due_at",
    "start_at",
    "end_at",
    "started_at",
    "ended_at",
    "all_day",
    "timezone",
}


# ─── 스키마 ───


class EventCreate(BaseModel):
    title: str
    description: str | None = None
    kind: str  # task | calendar_event | activity_log
    status: str | None = None  # 미지정 시 kind 별 default
    due_at: datetime | None = None
    start_at: datetime | None = None
    end_at: datetime | None = None
    started_at: datetime | None = None
    ended_at: datetime | None = None
    all_day: bool = False
    timezone: str | None = None
    priority: int | None = None
    project_tag: str | None = None
    tags: list[Any] = Field(default_factory=list)
    memo_document_id: int | None = None
    source: str = "manual"
    source_ref: str | None = None
    raw_metadata: dict[str, Any] = Field(default_factory=dict)


class EventPatch(BaseModel):
    """PATCH 허용 필드만. status/completed_at 등 lifecycle 필드는 명시 거부."""

    title: str | None = None
    description: str | None = None
    due_at: datetime | None = None
    start_at: datetime | None = None
    end_at: datetime | None = None
    started_at: datetime | None = None
    ended_at: datetime | None = None
    all_day: bool | None = None
    timezone: str | None = None
    priority: int | None = None
    project_tag: str | None = None
    tags: list[Any] | None = None
    memo_document_id: int | None = None

    model_config = {"extra": "forbid"}  # 허용 외 필드 → 422


class DeferRequest(BaseModel):
    defer_until: datetime


class EventResponse(BaseModel):
    id: int
    title: str
    description: str | None
    kind: str
    status: str
    due_at: datetime | None
    start_at: datetime | None
    end_at: datetime | None
    started_at: datetime | None
    ended_at: datetime | None
    all_day: bool
    timezone: str | None
    defer_until: datetime | None
    completed_at: datetime | None
    cancelled_at: datetime | None
    priority: int | None
    project_tag: str | None
    tags: list[Any]
    source: str
    source_ref: str | None
    raw_metadata: dict[str, Any]
    memo_document_id: int | None
    user_id: int
    created_by: str
    created_at: datetime
    updated_at: datetime


class EventListResponse(BaseModel):
    items: list[EventResponse]
    total: int


class EventHistoryResponse(BaseModel):
    id: int
    event_id: int
    changed_at: datetime
    changed_by: str
    change_kind: str
    before: dict[str, Any] | None
    after: dict[str, Any]


class EventHistoryListResponse(BaseModel):
    items: list[EventHistoryResponse]


# ─── 헬퍼 ───


def _to_response(ev: Event) -> EventResponse:
    return EventResponse.model_validate(ev, from_attributes=True)


def _serialize_for_history(ev: Event) -> dict[str, Any]:
    """events_history.before/after 용 dict snapshot (JSON 친화)."""
    payload: dict[str, Any] = {}
    for col in (
        "id",
        "title",
        "description",
        "kind",
        "status",
        "due_at",
        "start_at",
        "end_at",
        "started_at",
        "ended_at",
        "all_day",
        "timezone",
        "defer_until",
        "completed_at",
        "cancelled_at",
        "priority",
        "project_tag",
        "tags",
        "source",
        "source_ref",
        "raw_metadata",
        "memo_document_id",
        "user_id",
        "created_by",
    ):
        v = getattr(ev, col, None)
        if isinstance(v, datetime):
            payload[col] = v.isoformat()
        else:
            payload[col] = v
    return payload


def _actor_for_user(user: User) -> str:
    """사용자 직접 호출 = manual. 향후 이드/email_ingest 는 service token 분기 (PR-3)."""
    return "manual"


async def _record_history(
    session: AsyncSession,
    *,
    event: Event,
    change_kind: str,
    changed_by: str,
    before: dict[str, Any] | None,
    after: dict[str, Any],
) -> None:
    history = EventHistory(
        event_id=event.id,
        changed_by=changed_by,
        change_kind=change_kind,
        before=before,
        after=after,
    )
    session.add(history)


async def _load_owned(
    session: AsyncSession, event_id: int, user: User
) -> Event:
    ev = await session.get(Event, event_id)
    if ev is None or ev.user_id != user.id:
        raise HTTPException(status_code=404, detail="event not found")
    return ev


def _resolve_timezone(tz_name: str | None) -> ZoneInfo:
    try:
        return ZoneInfo(tz_name or DEFAULT_TIMEZONE)
    except Exception:
        raise HTTPException(status_code=400, detail=f"invalid timezone: {tz_name}")


def _local_day_bounds(tz_name: str | None) -> tuple[datetime, datetime, datetime]:
    """today 의 [start_utc, end_utc) + now_utc 반환."""
    tz = _resolve_timezone(tz_name)
    now_local = datetime.now(tz)
    today_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
    tomorrow_local = today_local + timedelta(days=1)
    return (
        today_local.astimezone(timezone.utc),
        tomorrow_local.astimezone(timezone.utc),
        now_local.astimezone(timezone.utc),
    )


def _apply_activity_log_defaults(payload: dict[str, Any]) -> None:
    """빠른 행동 기록 5초 UX — kind=activity_log 시 status/시간 default."""
    if payload.get("kind") != "activity_log":
        return
    now = datetime.now(timezone.utc)
    if not payload.get("status"):
        payload["status"] = "done"
    if payload.get("ended_at") is None:
        payload["ended_at"] = now
    if payload.get("started_at") is None:
        payload["started_at"] = payload["ended_at"]
    if payload.get("status") == "done":
        payload.setdefault("completed_at", now)


def _apply_kind_default_status(payload: dict[str, Any]) -> None:
    """kind 별 status default 보정."""
    if payload.get("status"):
        return
    kind = payload.get("kind")
    if kind == "calendar_event":
        payload["status"] = "scheduled"
    elif kind == "task":
        payload["status"] = "inbox"


# ─── Create ───


@router.post("/", response_model=EventResponse, status_code=201)
async def create_event(
    body: EventCreate,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    """events 생성. kind=activity_log 면 status=done/ended_at=now() default."""
    payload = body.model_dump(exclude_none=False)
    _apply_activity_log_defaults(payload)
    _apply_kind_default_status(payload)

    if payload["kind"] not in ("task", "calendar_event", "activity_log"):
        raise HTTPException(status_code=400, detail="invalid kind")

    actor = _actor_for_user(user)
    ev = Event(
        title=payload["title"],
        description=payload.get("description"),
        kind=payload["kind"],
        status=payload.get("status") or "inbox",
        due_at=payload.get("due_at"),
        start_at=payload.get("start_at"),
        end_at=payload.get("end_at"),
        started_at=payload.get("started_at"),
        ended_at=payload.get("ended_at"),
        all_day=payload.get("all_day") or False,
        timezone=payload.get("timezone"),
        completed_at=payload.get("completed_at"),
        priority=payload.get("priority"),
        project_tag=payload.get("project_tag"),
        tags=payload.get("tags") or [],
        source=payload.get("source") or "manual",
        source_ref=payload.get("source_ref"),
        raw_metadata=payload.get("raw_metadata") or {},
        memo_document_id=payload.get("memo_document_id"),
        user_id=user.id,
        created_by=actor,
    )
    session.add(ev)
    await session.flush()

    await _record_history(
        session,
        event=ev,
        change_kind="create",
        changed_by=actor,
        before=None,
        after=_serialize_for_history(ev),
    )
    await session.commit()
    await session.refresh(ev)
    return _to_response(ev)


# ─── List / Get ───


@router.get("/", response_model=EventListResponse)
async def list_events(
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
    kind: str | None = Query(None),
    status: str | None = Query(None, description="comma-separated list"),
    from_: datetime | None = Query(None, alias="from"),
    to: datetime | None = Query(None),
    project_tag: str | None = Query(None),
    source: str | None = Query(None),
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=200),
):
    """events 목록 — current_user.id 자동 필터. upcoming 은 ?from=now&to=now+7d 로."""
    where = [Event.user_id == user.id]
    if kind:
        where.append(Event.kind == kind)
    if status:
        statuses = [s.strip() for s in status.split(",") if s.strip()]
        if statuses:
            where.append(Event.status.in_(statuses))
    if project_tag:
        where.append(Event.project_tag == project_tag)
    if source:
        where.append(Event.source == source)
    if from_ is not None:
        # task: due_at, calendar_event: start_at, activity_log: started_at
        where.append(
            or_(
                Event.due_at >= from_,
                Event.start_at >= from_,
                Event.started_at >= from_,
            )
        )
    if to is not None:
        where.append(
            or_(
                Event.due_at < to,
                Event.start_at < to,
                Event.started_at < to,
            )
        )

    base = select(Event).where(and_(*where))
    total_q = await session.execute(
        select(Event.id).where(and_(*where))
    )
    total = len(total_q.scalars().all())

    rows = await session.execute(
        base.order_by(Event.created_at.desc())
        .offset((page - 1) * page_size)
        .limit(page_size)
    )
    items = [_to_response(e) for e in rows.scalars().all()]
    return EventListResponse(items=items, total=total)


@router.get("/today", response_model=EventListResponse)
async def list_today(
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
    timezone: str | None = Query(None, description="기본 Asia/Seoul"),
):
    """오늘 해야 할 것 / 예정된 것. timezone 적용.

    포함: task(due_at today) / calendar_event(start_at today) / activity_log(started_at today)
    status: inbox/next/scheduled/in_progress 또는 deferred (defer_until <= now() 일 때만).
    """
    start_utc, end_utc, now_utc = _local_day_bounds(timezone)

    today_clause = or_(
        and_(Event.kind == "task", Event.due_at >= start_utc, Event.due_at < end_utc),
        and_(
            Event.kind == "calendar_event",
            Event.start_at >= start_utc,
            Event.start_at < end_utc,
        ),
        and_(
            Event.kind == "activity_log",
            Event.started_at >= start_utc,
            Event.started_at < end_utc,
        ),
    )
    active_clause = or_(
        Event.status.in_(("inbox", "next", "scheduled", "in_progress")),
        and_(Event.status == "deferred", Event.defer_until <= now_utc),
    )
    rows = await session.execute(
        select(Event)
        .where(Event.user_id == user.id, today_clause, active_clause)
        .order_by(Event.start_at.asc(), Event.due_at.asc(), Event.started_at.asc())
    )
    items = [_to_response(e) for e in rows.scalars().all()]
    return EventListResponse(items=items, total=len(items))


@router.get("/inbox", response_model=EventListResponse)
async def list_inbox(
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    """Inbox — 아직 정리 안 된 것."""
    rows = await session.execute(
        select(Event)
        .where(Event.user_id == user.id, Event.status == "inbox")
        .order_by(Event.created_at.desc())
    )
    items = [_to_response(e) for e in rows.scalars().all()]
    return EventListResponse(items=items, total=len(items))


@router.get("/activity", response_model=EventListResponse)
async def list_activity(
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
    from_: datetime | None = Query(None, alias="from"),
    to: datetime | None = Query(None),
):
    """Activity timeline — 한 일 (kind=activity_log + status=done). Today 와 분리."""
    where = [
        Event.user_id == user.id,
        Event.kind == "activity_log",
        Event.status == "done",
    ]
    if from_ is not None:
        where.append(Event.started_at >= from_)
    if to is not None:
        where.append(Event.started_at < to)
    rows = await session.execute(
        select(Event).where(and_(*where)).order_by(Event.started_at.desc())
    )
    items = [_to_response(e) for e in rows.scalars().all()]
    return EventListResponse(items=items, total=len(items))


@router.get("/{event_id}", response_model=EventResponse)
async def get_event(
    event_id: int,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    ev = await _load_owned(session, event_id, user)
    return _to_response(ev)


@router.get("/{event_id}/history", response_model=EventHistoryListResponse)
async def get_event_history(
    event_id: int,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    """events_history 조회 — 상세 페이지 timeline. lifecycle op 자동 기록만 (v1)."""
    await _load_owned(session, event_id, user)  # owner 검증
    rows = await session.execute(
        select(EventHistory)
        .where(EventHistory.event_id == event_id)
        .order_by(EventHistory.changed_at.desc())
    )
    items = [
        EventHistoryResponse.model_validate(h, from_attributes=True)
        for h in rows.scalars().all()
    ]
    return EventHistoryListResponse(items=items)


# ─── PATCH ───


@router.patch("/{event_id}", response_model=EventResponse)
async def patch_event(
    event_id: int,
    body: EventPatch,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    """PATCH — 허용 필드만. 시간 필드 변경 시 reschedule history 자동 기록.

    status/completed_at/cancelled_at/defer_until 등 lifecycle 필드는 별 endpoint 강제.
    """
    ev = await _load_owned(session, event_id, user)

    patch = body.model_dump(exclude_unset=True)
    if not patch:
        return _to_response(ev)

    # 안전 검사 — extra=forbid 로 막혀 있지만 한 번 더.
    for k in patch:
        if k not in PATCH_ALLOWED_FIELDS:
            raise HTTPException(status_code=400, detail=f"field not patchable: {k}")

    time_changed = any(k in RESCHEDULE_TIME_FIELDS for k in patch)
    before_snapshot = _serialize_for_history(ev) if time_changed else None

    for k, v in patch.items():
        setattr(ev, k, v)
    await session.flush()

    if time_changed:
        actor = _actor_for_user(user)
        await _record_history(
            session,
            event=ev,
            change_kind="reschedule",
            changed_by=actor,
            before=before_snapshot,
            after=_serialize_for_history(ev),
        )

    await session.commit()
    await session.refresh(ev)
    return _to_response(ev)


# ─── Lifecycle ───


async def _transition(
    session: AsyncSession,
    *,
    event: Event,
    change_kind: str,
    new_status: str,
    user: User,
    extra_apply: dict[str, Any] | None = None,
) -> Event:
    actor = _actor_for_user(user)
    before = _serialize_for_history(event)
    event.status = new_status
    if extra_apply:
        for k, v in extra_apply.items():
            setattr(event, k, v)
    await session.flush()
    await _record_history(
        session,
        event=event,
        change_kind=change_kind,
        changed_by=actor,
        before=before,
        after=_serialize_for_history(event),
    )
    return event


@router.post("/{event_id}/complete", response_model=EventResponse)
async def complete_event(
    event_id: int,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    ev = await _load_owned(session, event_id, user)
    now = datetime.now(timezone.utc)
    await _transition(
        session,
        event=ev,
        change_kind="complete",
        new_status="done",
        user=user,
        extra_apply={"completed_at": now},
    )
    await session.commit()
    await session.refresh(ev)
    return _to_response(ev)


@router.post("/{event_id}/cancel", response_model=EventResponse)
async def cancel_event(
    event_id: int,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    ev = await _load_owned(session, event_id, user)
    now = datetime.now(timezone.utc)
    await _transition(
        session,
        event=ev,
        change_kind="cancel",
        new_status="cancelled",
        user=user,
        extra_apply={"cancelled_at": now},
    )
    await session.commit()
    await session.refresh(ev)
    return _to_response(ev)


@router.post("/{event_id}/defer", response_model=EventResponse)
async def defer_event(
    event_id: int,
    body: DeferRequest,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    ev = await _load_owned(session, event_id, user)
    await _transition(
        session,
        event=ev,
        change_kind="defer",
        new_status="deferred",
        user=user,
        extra_apply={"defer_until": body.defer_until},
    )
    await session.commit()
    await session.refresh(ev)
    return _to_response(ev)


@router.post("/{event_id}/reactivate", response_model=EventResponse)
async def reactivate_event(
    event_id: int,
    user: Annotated[User, Depends(get_current_user)],
    session: Annotated[AsyncSession, Depends(get_session)],
):
    """완료/취소/연기 해제 — kind 따라 기본 status 복귀.

    task: inbox, calendar_event: scheduled, activity_log: done 유지 안 함 (activity_log 는 done 이 자연 상태이므로 reactivate 적용 X → 400).
    """
    ev = await _load_owned(session, event_id, user)
    if ev.kind == "activity_log":
        raise HTTPException(
            status_code=400, detail="activity_log 는 reactivate 대상 아님"
        )
    new_status = "scheduled" if ev.kind == "calendar_event" else "inbox"
    await _transition(
        session,
        event=ev,
        change_kind="reactivate",
        new_status=new_status,
        user=user,
        extra_apply={"completed_at": None, "cancelled_at": None, "defer_until": None},
    )
    await session.commit()
    await session.refresh(ev)
    return _to_response(ev)
