# backend/app/worker/sync_jobs.py — phase-13 주기 sync 잡 + 수동 트리거 진입점
# 프로토타입은 수동 트리거(/api/connectors/sync-all)도 제공(데모 결정성).
from sqlmodel import Session, select

from ..config import get_settings
from ..connectors.registry import ConnectorRegistry
from ..db import engine
from ..models import ConnState, Email, ExternalLink


def recover_stuck_syncing(session: Session | None = None) -> int:
    """기동 시 'syncing' 으로 멈춰 있는 계정을 'connected' 로 되돌린다.
    sync() 는 시작할 때 state=syncing 을 즉시 커밋하고 끝에서 connected/error 로 바꾸는데,
    그 사이에 프로세스가 죽거나(uvicorn --reload, 크래시) 재시작되면 syncing 이 DB 에 영구히 남는다.
    run_periodic_sync 는 connected/error 만 sync 하므로 멈춘 syncing 계정은 영영 동기화에서 빠진다.
    기동 직후엔 실제 진행 중인 sync 가 없으므로 syncing 은 모두 '깨진 상태' → connected 로 복구."""

    def _do(s: Session) -> int:
        stuck = [a for a in ConnectorRegistry.accounts(s) if a.state == ConnState.syncing]
        for a in stuck:
            a.state = ConnState.connected
            s.add(a)
        if stuck:
            s.commit()
        return len(stuck)

    if session is not None:
        return _do(session)
    with Session(engine) as s:
        return _do(s)


def purge_spam(session: Session | None = None) -> int:
    """이미 저장된 스팸(provider 가 스팸 분류)을 일괄 삭제 — 더는 스팸을 들고 있지 않는다.
    이후 sync 는 스팸을 저장하지 않으므로(fetch 제외 + base.sync 안전망) 재유입 없음."""

    def _do(s: Session) -> int:
        spam = s.exec(select(Email).where(Email.folder == "spam")).all()
        ids = [e.id for e in spam]
        if not ids:
            return 0
        links = s.exec(
            select(ExternalLink).where(
                ExternalLink.entity_type == "email",
                ExternalLink.entity_id.in_(ids),
            )
        ).all()
        for lk in links:
            s.delete(lk)
        for e in spam:
            s.delete(e)
        s.commit()
        return len(ids)

    if session is not None:
        return _do(session)
    with Session(engine) as s:
        return _do(s)


def run_periodic_sync(session: Session | None = None) -> list[dict]:
    """worker 스케줄러가 sync_interval_minutes 마다 호출. real/연결된 계정만 sync.
    mock 계정은 데이터가 변하지 않으므로 주기 sync 대상이 아니다."""

    def _do(s: Session) -> list[dict]:
        out: list[dict] = []
        for a in ConnectorRegistry.accounts(s):
            mode = ConnectorRegistry.effective_mode(a)
            if a.state in (ConnState.connected, ConnState.error) and mode != "mock":
                res = ConnectorRegistry.get(s, a).sync(s)
                out.append(res.__dict__)
        return out

    if session is not None:
        return _do(session)
    with Session(engine) as s:
        return _do(s)


def run_auto_triage(limit: int = 3) -> int:
    """새 메일 자동 정리: 기준 시각 이후 도착한 미분석 '집중' 메일을 LLM 으로 백그라운드 분석.
    auto-sync 루프가 sync 직후 호출 — 사용자가 '정리 맡기기'를 누르지 않아도 카드가 미리 채워진다.
    한 사이클에 limit 통만 처리(부하 제한). LLM 미가용/오류 시 즉시 중단(다음 주기 재시도)."""
    # 라우터 헬퍼(폴더 판정·기준 시각·정렬)를 재사용. 순환 임포트 방지로 함수 안에서 지연 임포트.
    from ..llm.provider import get_provider
    from ..models import TriageEvent
    from ..routers.mail import (
        _ai_actionable,
        _email_in_folder,
        _email_sort_key,
        _ensure_triage_since,
        _is_new_mail,
    )
    from ..services.mail_ai import analyze_email

    with Session(engine) as s:
        since = _ensure_triage_since(s)
        todo = [
            e
            for e in s.exec(select(Email)).all()
            if not e.ai_json and _email_in_folder(e, "smart") and _is_new_mail(e, since)
        ]
        if not todo:
            return 0
        todo.sort(key=_email_sort_key, reverse=True)
        provider = get_provider()
        analyzed, cards, err = 0, 0, None
        for e in todo[:limit]:
            try:
                ai = analyze_email(s, e, provider)
                analyzed += 1
                if _ai_actionable(ai):
                    cards += 1
            except Exception as ex:  # LLM 미가용/파싱 실패 — 이번 사이클 중단, 다음 주기에 재시도
                err = (str(ex) or ex.__class__.__name__)[:200]
                break
        # '오늘 한 일' 로그: 실제로 읽었거나 에러가 났을 때만 기록(무활동은 남기지 않음).
        if analyzed or err:
            s.add(TriageEvent(analyzed=analyzed, cards=cards, error=err))
            s.commit()
        return analyzed


def register(scheduler) -> None:
    """phase-7 worker.main(APScheduler)에서 호출(있을 때). 없으면 수동 트리거로 동작."""
    mins = get_settings().sync_interval_minutes
    scheduler.add_job(
        run_periodic_sync, "interval", minutes=mins, id="connector_sync", replace_existing=True
    )
