# backend/app/connectors/calendar/normalize.py — provider → cal_event(phase-8) 정규화 + upsert
import hashlib
import uuid

from sqlmodel import Session, select

from ...models import Calendar, CalEvent, ExternalLink
from ..base import NormalizedRecord, RawRecord
from .recurrence import graph_to_rrule


def _google_attendees(p: dict) -> tuple[list, str]:
    """Google attendees → ([{email,name,status}], 내 응답상태)."""
    out, mine = [], ""
    for a in p.get("attendees", []) or []:
        email = a.get("email", "")
        if not email:
            continue
        status = a.get("responseStatus", "needsAction")
        out.append({"email": email, "name": a.get("displayName", ""), "status": status})
        if a.get("self"):
            mine = status
    return out, mine


def _outlook_attendees(p: dict) -> tuple[list, str]:
    """Outlook attendees → ([{email,name,status}], 내 응답상태)."""
    out = []
    for a in p.get("attendees", []) or []:
        ea = a.get("emailAddress") or {}
        addr = ea.get("address", "")
        if not addr:
            continue
        out.append(
            {
                "email": addr,
                "name": ea.get("name", ""),
                "status": (a.get("status") or {}).get("response", "none"),
            }
        )
    mine = (p.get("responseStatus") or {}).get("response", "")
    return out, mine

# Google Calendar 카테고리 추정 → 내부 calendar.id (없으면 work)
_VALID_CALS = {"work", "meeting", "personal", "team", "health"}

# provider → (calendar id prefix, 표시 라벨, 색 tone)
_CAL_META = {
    "google_calendar": ("gcal", "Google 캘린더", "blue"),
    "outlook_calendar": ("ocal", "Outlook 캘린더", "coral"),
}


def account_calendar_id(account) -> str:
    """연결된 캘린더 계정별 고유 캘린더 id(이메일 슬러그). 이벤트가 이 캘린더에 묶인다.
    Google/Outlook 모두 계정마다 별도 캘린더로 분리(같은 색·이름 충돌 방지)."""
    provider = getattr(account, "provider", "") or ""
    prefix = _CAL_META.get(provider, ("cal", "캘린더", "blue"))[0]
    email = (getattr(account, "external_account_id", "") or "").lower()
    return f"{prefix}-{hashlib.sha1(email.encode()).hexdigest()[:8]}" if email else prefix


def ensure_calendar_row(session: Session, account) -> str:
    """연결된 캘린더 계정의 Calendar 행을 보장(없으면 생성, 있으면 이름/색 갱신).
    연결 직후 즉시 호출 → 동기화 전이라도 일정 화면 토글에 계정이 노출된다."""
    cid = account_calendar_id(account)
    _prefix, label, tone = _CAL_META.get(account.provider, ("cal", "캘린더", "blue"))
    email = getattr(account, "external_account_id", "") or ""
    name = f"{label} · {email}" if email else label
    cal = session.get(Calendar, cid)
    if cal:
        cal.name, cal.tone = name, tone
        session.add(cal)
    else:
        session.add(Calendar(id=cid, name=name, tone=tone, on=True, sort_order=50))
    session.commit()
    return cid


# 하위호환 별칭(real_google.py 등 기존 import 보존)
gcal_calendar_id = account_calendar_id
ensure_gcal_calendar = ensure_calendar_row


# ── 이벤트 카테고리 분류(공휴일/생일/Gmail 자동 일정 등) → 토글로 숨김 가능 ──
# category id → (표시 이름, 색 tone). 사이드바 캘린더 토글에 그대로 노출된다.
_CATEGORY_META = {
    "cat-holiday": ("공휴일", "coral"),
    "cat-birthday": ("생일", "violet"),
    "cat-gmail": ("Gmail 자동 일정", "faint"),
    "cat-ooo": ("자리비움·집중", "amber"),
}
# 제목 기반 공휴일 키워드(휴일 캘린더 미구독 시 폴백).
_HOLIDAY_WORDS = (
    "공휴일", "신정", "설날", "삼일절", "어린이날", "부처님", "현충일", "광복절",
    "추석", "개천절", "한글날", "성탄절", "크리스마스", "대체공휴일", "제헌절",
    "holiday", "christmas", "new year",
)


def classify_event(provider: str, p: dict, title: str) -> str | None:
    """프로바이더 신호(eventType/categories) + 제목으로 특수 카테고리를 판정. 없으면 None."""
    t = (title or "").lower()
    if provider == "google_calendar":
        et = p.get("eventType", "default")
        if et == "birthday":
            return "cat-birthday"
        if et == "fromGmail":
            return "cat-gmail"
        if et in ("outOfOffice", "focusTime", "workingLocation"):
            return "cat-ooo"
    elif provider == "outlook_calendar":
        cats = [c.lower() for c in (p.get("categories") or [])]
        if any(("birthday" in c or "생일" in c) for c in cats):
            return "cat-birthday"
        if any(("holiday" in c or "휴일" in c) for c in cats):
            return "cat-holiday"
    # 공통 제목 휴리스틱
    if "생일" in (title or "") or "birthday" in t:
        return "cat-birthday"
    if any(w in (title or "") or w in t for w in _HOLIDAY_WORDS):
        return "cat-holiday"
    return None


def ensure_category_calendar(session: Session, cat_id: str) -> None:
    """특수 카테고리 Calendar 행 보장(사이드바 토글로 숨김/표시 가능하게)."""
    meta = _CATEGORY_META.get(cat_id)
    if not meta or session.get(Calendar, cat_id):
        return
    name, tone = meta
    session.add(Calendar(id=cat_id, name=name, tone=tone, on=True, sort_order=80))


def _hhmm(iso: str) -> str:
    # "2026-06-10T09:30:00+09:00" → "09:30"
    if "T" in iso:
        return iso.split("T", 1)[1][:5]
    return ""


def _day(iso: str) -> int:
    # "2026-06-10..." → 10
    try:
        return int(iso[8:10])
    except (ValueError, IndexError):
        return 0


def normalize_event(account, raw: RawRecord, provider: str = "mock") -> NormalizedRecord:
    p = raw.payload
    if provider == "google_calendar":
        start = p.get("start", {}).get("dateTime", p.get("start", {}).get("date", ""))
        end = p.get("end", {}).get("dateTime", p.get("end", {}).get("date", ""))
        attendees, mine = _google_attendees(p)
        rec = p.get("recurrence") or []
        rrule = rec[0].replace("RRULE:", "") if rec else ""
        reminders = [
            o["minutes"]
            for o in (p.get("reminders") or {}).get("overrides", [])
            if "minutes" in o
        ]
        fields = {
            "day": _day(start),
            "date": start[:10],  # "2026-06-16T.." / "2026-06-16" → "2026-06-16"
            "start": _hhmm(start),
            "end": _hhmm(end),
            "title": p.get("summary", "(제목 없음)"),
            "cal": gcal_calendar_id(account),  # 연결된 Google 계정 캘린더로 귀속
            "loc": p.get("location", ""),
            "note": p.get("description", ""),
            "soon": False,
            "people": ", ".join(a["email"] for a in attendees),
            "rrule": rrule,
            "reminders": reminders,
            "attendees": attendees,
            "response_status": mine,
        }
    elif provider == "outlook_calendar":  # Microsoft Graph event
        start = p.get("start", {}).get("dateTime", "")
        end = p.get("end", {}).get("dateTime", "")
        attendees, mine = _outlook_attendees(p)
        reminders = (
            [p["reminderMinutesBeforeStart"]]
            if p.get("isReminderOn") and p.get("reminderMinutesBeforeStart") is not None
            else []
        )
        fields = {
            "day": _day(start),
            "date": start[:10],
            "start": _hhmm(start),
            "end": _hhmm(end),
            "title": p.get("subject") or "(제목 없음)",
            "cal": account_calendar_id(account),  # 연결된 Outlook 계정 캘린더로 귀속
            "loc": (p.get("location") or {}).get("displayName", ""),
            # 조회=bodyPreview, 생성/수정 응답=body.content 폴백.
            "note": p.get("bodyPreview") or (p.get("body") or {}).get("content", ""),
            "soon": False,
            "people": ", ".join(a["email"] for a in attendees),
            "rrule": graph_to_rrule(p.get("recurrence")),
            "reminders": reminders,
            "attendees": attendees,
            "response_status": mine,
        }
    else:  # mock / ics — 이미 내부 키와 유사(day/start/end/title/cal/loc/note/soon/people)
        cal = (p.get("cal") or "work").lower()
        fields = {
            "day": int(p.get("day", 0)),
            "start": p.get("start", ""),
            "end": p.get("end", ""),
            "title": p.get("title", ""),
            "cal": cal if cal in _VALID_CALS else "work",
            "loc": p.get("loc", ""),
            "note": p.get("note", ""),
            "soon": bool(p.get("soon", False)),
            "people": p.get("people", ""),
        }
    # 특수 카테고리(공휴일/생일/Gmail 자동 일정 등)면 전용 캘린더로 귀속 → 토글로 숨김 가능.
    if provider in ("google_calendar", "outlook_calendar"):
        cat = classify_event(provider, p, fields["title"])
        if cat:
            fields["cal"] = cat
    return NormalizedRecord(
        entity_type="event",
        external_id=raw.external_id,
        fields=fields,
        etag=raw.etag,
        external_updated_at=raw.external_updated_at,
    )


def ensure_external_link(
    session: Session, account, external_id: str, entity_type: str, entity_id: str, etag: str = ""
) -> None:
    """직접 생성/수정한 이벤트를 (account, external_id)로 멱등 매핑(재동기화 중복 방지)."""
    link = session.exec(
        select(ExternalLink).where(
            ExternalLink.account_id == account.id,
            ExternalLink.external_id == external_id,
        )
    ).first()
    if link:
        link.etag = etag
        link.entity_id = entity_id
        session.add(link)
        return
    xlid = "xl-" + hashlib.sha1(f"{account.id}:{external_id}".encode()).hexdigest()[:12]
    session.add(
        ExternalLink(
            id=xlid,
            account_id=account.id,
            external_id=external_id,
            entity_type=entity_type,
            entity_id=entity_id,
            etag=etag,
        )
    )


def upsert_event(session: Session, norm: NormalizedRecord) -> tuple[str, bool]:
    cal = norm.fields.get("cal", "")
    if isinstance(cal, str) and cal.startswith("cat-"):
        ensure_category_calendar(session, cal)
    link = session.exec(
        select(ExternalLink).where(
            ExternalLink.external_id == norm.external_id,
            ExternalLink.entity_type == "event",
        )
    ).first()
    if link:
        ev = session.get(CalEvent, link.entity_id)
        if ev:
            for k, v in norm.fields.items():
                if v is not None and hasattr(ev, k):
                    setattr(ev, k, v)
            session.add(ev)
            return ev.id, False
    eid = "ce-" + uuid.uuid4().hex[:8]
    session.add(CalEvent(id=eid, **{k: v for k, v in norm.fields.items() if v is not None}))
    return eid, True
