# backend/tests/test_phase18_features.py — phase-18 신규 기능
# 메일(검색·일괄·라벨·cc) · 일정(반복·알림·RSVP) · 작업(정렬·댓글 수정/삭제) · 반복규칙 변환
from app.connectors.calendar.normalize import classify_event
from app.connectors.calendar.recurrence import (
    graph_to_rrule,
    parse_rrule,
    rrule_to_google,
    rrule_to_graph,
)
from tests._factories import make_comment, make_email, make_mail_account, make_task


# ── 일정 카테고리 분류(공휴일/생일/Gmail) ──
def test_classify_google_birthday_and_gmail():
    assert classify_event("google_calendar", {"eventType": "birthday"}, "축하") == "cat-birthday"
    assert classify_event("google_calendar", {"eventType": "fromGmail"}, "예약") == "cat-gmail"
    assert classify_event("google_calendar", {"eventType": "default"}, "주간 회의") is None


def test_classify_by_title_heuristic():
    assert classify_event("google_calendar", {"eventType": "default"}, "유나 생일") == "cat-birthday"
    assert classify_event("google_calendar", {"eventType": "default"}, "추석 연휴") == "cat-holiday"


def test_classify_outlook_categories():
    p = {"categories": ["Holiday"]}
    assert classify_event("outlook_calendar", p, "Christmas") == "cat-holiday"


# ── 반복 규칙(RRULE) 변환: Google/Graph 양방향 ──
def test_rrule_to_google_wraps_prefix():
    assert rrule_to_google("FREQ=WEEKLY;BYDAY=MO") == ["RRULE:FREQ=WEEKLY;BYDAY=MO"]
    assert rrule_to_google("") == []


def test_rrule_graph_roundtrip_weekly():
    g = rrule_to_graph("FREQ=WEEKLY;BYDAY=MO,WE;INTERVAL=2", "2026-06-15")
    assert g["pattern"]["type"] == "weekly"
    assert g["pattern"]["interval"] == 2
    assert set(g["pattern"]["daysOfWeek"]) == {"monday", "wednesday"}
    back = graph_to_rrule(g)
    r = parse_rrule(back)
    assert r["FREQ"] == "WEEKLY" and r["INTERVAL"] == "2"
    assert set(r["BYDAY"]) == {"MO", "WE"}


def test_rrule_to_graph_monthly_uses_day_of_month():
    g = rrule_to_graph("FREQ=MONTHLY", "2026-06-15")
    assert g["pattern"]["type"] == "absoluteMonthly"
    assert g["pattern"]["dayOfMonth"] == 15


def test_rrule_to_graph_count_range():
    g = rrule_to_graph("FREQ=DAILY;COUNT=5", "2026-06-15")
    assert g["range"]["type"] == "numbered"
    assert g["range"]["numberOfOccurrences"] == 5


# ── 메일 서버측 검색 ──
def test_mail_search_filters_by_query(client, session):
    s, _ = session
    make_mail_account(s, id="work")
    make_email(s, id="m1", subject="분기 리포트 검토", preview="리포트 초안")
    make_email(s, id="m2", subject="점심 약속", preview="강남에서 봐요")
    hits = client.get("/api/mail?q=리포트").json()
    ids = {m["id"] for m in hits}
    assert ids == {"m1"}


# ── 메일 일괄 처리 ──
def test_mail_bulk_mark_read(client, session):
    s, _ = session
    make_mail_account(s, id="work")
    make_email(s, id="m1", read=False)
    make_email(s, id="m2", read=False)
    r = client.post("/api/mail/bulk", json={"ids": ["m1", "m2"], "action": "read"})
    assert r.status_code == 200 and r.json()["count"] == 2
    rows = {m["id"]: m for m in client.get("/api/mail").json()}
    assert rows["m1"]["read"] is True and rows["m2"]["read"] is True


def test_mail_bulk_trash_removes(client, session):
    s, _ = session
    make_mail_account(s, id="work")
    make_email(s, id="m1")
    client.post("/api/mail/bulk", json={"ids": ["m1"], "action": "trash"})
    assert client.get("/api/mail/m1").status_code == 404


def test_mail_bulk_invalid_action_400(client, session):
    s, _ = session
    make_email(s, id="m1")
    assert client.post("/api/mail/bulk", json={"ids": ["m1"], "action": "nope"}).status_code == 400


# ── 메일 라벨 추가/제거 ──
def test_mail_labels_add_remove(client, session):
    s, _ = session
    make_mail_account(s, id="work")
    make_email(s, id="m1", labels=["기존"])
    r = client.patch("/api/mail/m1/labels", json={"add": ["중요"], "remove": ["기존"]})
    assert r.status_code == 200
    assert r.json()["labels"] == ["중요"]


# ── 메일 cc 직렬화 ──
def test_mail_detail_exposes_cc(client, session):
    s, _ = session
    make_mail_account(s, id="work")
    make_email(s, id="m1")
    e = client.get("/api/mail/m1").json()
    assert "cc" in e and "thread_id" in e


# ── 일정 생성: 반복 + 알림 (로컬) ──
def test_create_event_with_recurrence_and_reminders(client):
    body = {
        "title": "주간 회의",
        "date": "2026-06-17",
        "start": "10:00",
        "end": "10:30",
        "rrule": "FREQ=WEEKLY",
        "reminders": [10, 30],
        "people": ["a@b.com"],
    }
    ev = client.post("/api/calendar/events", json=body).json()
    assert ev["rrule"] == "FREQ=WEEKLY"
    assert ev["reminders"] == [10, 30]
    assert ev["attendees"][0]["email"] == "a@b.com"


# ── 일정 RSVP (로컬) ──
def test_event_rsvp_updates_response(client):
    body = {"title": "초대받은 회의", "date": "2026-06-17", "start": "14:00", "end": "15:00",
            "people": ["host@x.com"]}
    ev = client.post("/api/calendar/events", json=body).json()
    r = client.post(f"/api/calendar/events/{ev['id']}/rsvp", json={"status": "accepted"})
    assert r.status_code == 200
    assert r.json()["response_status"] == "accepted"


def test_event_rsvp_invalid_status_400(client):
    body = {"title": "회의", "date": "2026-06-17", "start": "14:00", "end": "15:00"}
    ev = client.post("/api/calendar/events", json=body).json()
    assert client.post(
        f"/api/calendar/events/{ev['id']}/rsvp", json={"status": "maybe"}
    ).status_code == 400


# ── 작업 드래그 정렬 ──
def test_task_reorder_sets_sort_order(client, session):
    s, _ = session
    make_task(s, id="t1", project_id="me", sort_order=0)
    make_task(s, id="t2", project_id="me", sort_order=1)
    make_task(s, id="t3", project_id="me", sort_order=2)
    r = client.post("/api/tasks/reorder", json={"ids": ["t3", "t1", "t2"]})
    assert r.status_code == 200 and r.json()["count"] == 3
    order = {t["id"]: t["sort_order"] for t in client.get("/api/tasks").json()}
    assert order["t3"] == 0 and order["t1"] == 1 and order["t2"] == 2


# ── 댓글 수정/삭제 ──
def test_comment_edit_sets_edited_at(client, session):
    s, _ = session
    make_task(s, id="t1", project_id="me")
    make_comment(s, id="c1", task_id="t1", text="원본")
    r = client.patch("/api/tasks/t1/comments/c1", json={"text": "수정됨"})
    assert r.status_code == 200
    assert r.json()["text"] == "수정됨" and r.json()["edited_at"] is not None


def test_comment_delete(client, session):
    s, _ = session
    make_task(s, id="t1", project_id="me")
    make_comment(s, id="c1", task_id="t1")
    assert client.delete("/api/tasks/t1/comments/c1").status_code == 200
    node = client.get("/api/tasks/t1").json()
    assert node["comments"] == []


def test_comment_edit_wrong_task_404(client, session):
    s, _ = session
    make_task(s, id="t1", project_id="me")
    make_comment(s, id="c1", task_id="t1")
    assert client.patch("/api/tasks/t9/comments/c1", json={"text": "x"}).status_code == 404
