/**
 * Google Calendar (iCal) 캐시 — fetch + parse + 회사 _shared/calendar_cache.md 에 저장.
 *
 * Connect_origin 의 google_calendar.py 를 TypeScript / native fetch 로 옮김. OAuth 없음.
 * 사용자가 Google Calendar 설정 → "비공개 주소(iCal 형식)" 복사 → 본 모듈에 입력 한 번이면
 * 모든 agent 가 매 turn 자동으로 다가오는 일정 컨텍스트를 받는다.
 *
 * 보안:
 *   - iCal URL 은 ExtensionContext.globalState 에 저장 (machine-local, git 침범 X).
 *   - 캐시 파일은 회사 디렉토리 `_shared/calendar_cache.md` 에 평문 마크다운으로 저장.
 *     이 파일은 .gitignore 대상은 아니지만 일정 제목/시각이 들어있음 — 사용자가 commit
 *     안 하도록 가이드 문구를 README/명령에서 안내한다.
 */

import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { parseIcs, selectUpcoming, IcsEvent } from './icsParser';

/** globalState 키 — iCal URL 과 부수 설정 한 묶음. */
export const CAL_CONFIG_KEY = 'g1nation.calendar.ical';

export interface CalendarConfig {
    /** Google Calendar 비공개 iCal URL. 빈 문자열이면 iCal 읽기 비활성. */
    icalUrl: string;
    /** 며칠치 미리 가져올지 (default 14). */
    daysAhead: number;
    /** 마지막 성공 fetch ISO timestamp (자동 표시용). */
    lastFetchAt?: string;
    // ── OAuth (쓰기) 관련 필드 — Google Calendar API v3 호출에 사용. ──
    // 모두 ExtensionContext.globalState 에만 저장 (machine-local). 옵션이라 비어있어도 iCal 읽기는 동작.
    /** Google Cloud Console 에서 발급한 OAuth Client ID. */
    clientId?: string;
    /** 같은 페이지의 Client Secret (Desktop app 의 secret 은 공개 가능한 식별자). */
    clientSecret?: string;
    /** OAuth 로 받은 refresh token — 진짜 비밀. machine-local. */
    refreshToken?: string;
    /** Calendar API 가 쓰는 캘린더 식별자 — 'primary' 또는 특정 calendarId. */
    calendarId?: string;
    /** end 없는 이벤트 default 길이 (분). */
    defaultDurationMinutes?: number;
    /** 캐시된 access token (만료 전까지 재사용). */
    accessToken?: string;
    /** access token 만료 epoch ms. */
    accessTokenExpiresAt?: number;
    /** 연결된 Google 계정 이메일 (UI 표시용). */
    connectedAs?: string;
    /** OAuth 연결 시각 ISO. */
    connectedAt?: string;
}

/**
 * Settings + globalState 두 곳에서 읽어 merge.
 *
 *   • VS Code Settings (g1nation.google.*) — 사용자가 직접 편집 가능한 필드:
 *       clientId, clientSecret, calendarId, defaultDurationMinutes, icalUrl, daysAhead
 *     이 값이 채워져 있으면 globalState 의 같은 필드보다 *우선*.
 *
 *   • globalState (CAL_CONFIG_KEY) — 마법사가 자동 관리하는 secret / runtime 필드:
 *       refreshToken, accessToken, accessTokenExpiresAt, connectedAs, connectedAt, lastFetchAt
 *     사용자는 settings 에서 안 보임. 마법사가 OAuth 완료 후 자동 기록.
 *
 *   • 옛 사용자 호환: globalState 에 clientId 같은 게 남아있어도 settings 가 비면
 *     globalState 값으로 fallback. 명시적으로 settings 에 비워두면 globalState 도 무시.
 */
export function readCalendarConfig(context: vscode.ExtensionContext): CalendarConfig {
    const raw = (context.globalState.get(CAL_CONFIG_KEY) as Partial<CalendarConfig> | undefined) ?? {};
    const s = vscode.workspace.getConfiguration('g1nation.google');
    const fromSettings = <T>(key: string): T | undefined => {
        const v = s.get<T>(key);
        // 빈 문자열은 "미설정" 으로 취급 — 사용자가 지운 케이스.
        if (typeof v === 'string' && v.trim() === '') return undefined;
        return v;
    };
    const clientId = fromSettings<string>('clientId') ?? (typeof raw.clientId === 'string' ? raw.clientId : undefined);
    const clientSecret = fromSettings<string>('clientSecret') ?? (typeof raw.clientSecret === 'string' ? raw.clientSecret : undefined);
    const calendarId = fromSettings<string>('calendarId') ?? (typeof raw.calendarId === 'string' ? raw.calendarId : undefined);
    const defaultDurationMinutes = fromSettings<number>('defaultEventDurationMinutes')
        ?? (typeof raw.defaultDurationMinutes === 'number' ? raw.defaultDurationMinutes : undefined);
    const icalUrl = fromSettings<string>('icalUrl') ?? (typeof raw.icalUrl === 'string' ? raw.icalUrl : '');
    const daysAhead = fromSettings<number>('icalDaysAhead') ?? (typeof raw.daysAhead === 'number' && raw.daysAhead > 0 ? raw.daysAhead : 14);
    return {
        icalUrl: icalUrl ?? '',
        daysAhead: daysAhead ?? 14,
        lastFetchAt: typeof raw.lastFetchAt === 'string' ? raw.lastFetchAt : undefined,
        clientId,
        clientSecret,
        refreshToken: typeof raw.refreshToken === 'string' ? raw.refreshToken : undefined,
        calendarId,
        defaultDurationMinutes,
        accessToken: typeof raw.accessToken === 'string' ? raw.accessToken : undefined,
        accessTokenExpiresAt: typeof raw.accessTokenExpiresAt === 'number' ? raw.accessTokenExpiresAt : undefined,
        connectedAs: typeof raw.connectedAs === 'string' ? raw.connectedAs : undefined,
        connectedAt: typeof raw.connectedAt === 'string' ? raw.connectedAt : undefined,
    };
}

/**
 * Patch 의 필드를 settings 또는 globalState 의 적절한 곳에 분기 저장.
 *   • settings 로 가는 것 (사용자 편집 가능): clientId, clientSecret, calendarId,
 *     defaultDurationMinutes, icalUrl, daysAhead
 *   • globalState 로 가는 것 (secret / runtime): refreshToken, accessToken,
 *     accessTokenExpiresAt, connectedAs, connectedAt, lastFetchAt
 *
 * 한 번에 양쪽을 patch 해도 OK — 분기 자동.
 */
export async function writeCalendarConfig(context: vscode.ExtensionContext, patch: Partial<CalendarConfig>): Promise<void> {
    // Settings (g1nation.google.*) 로 가는 필드들.
    const s = vscode.workspace.getConfiguration('g1nation.google');
    const settingsKeys: Array<[keyof CalendarConfig, string]> = [
        ['clientId', 'clientId'],
        ['clientSecret', 'clientSecret'],
        ['calendarId', 'calendarId'],
        ['defaultDurationMinutes', 'defaultEventDurationMinutes'],
        ['icalUrl', 'icalUrl'],
        ['daysAhead', 'icalDaysAhead'],
    ];
    for (const [src, dst] of settingsKeys) {
        if (src in patch) {
            const v = (patch as any)[src];
            // undefined → settings 에서 제거 (default 로 복귀). 빈 문자열도 동일 취급.
            const toWrite = (v === undefined || v === '') ? undefined : v;
            try { await s.update(dst, toWrite, vscode.ConfigurationTarget.Global); }
            catch { /* settings 쓰기 실패 시 globalState 로 fallback (다음 read 가 globalState 봄). */
                const cur = (context.globalState.get(CAL_CONFIG_KEY) as Partial<CalendarConfig>) ?? {};
                await context.globalState.update(CAL_CONFIG_KEY, { ...cur, [src]: v });
            }
        }
    }
    // GlobalState 로 가는 secret / runtime 필드.
    const globalKeys: Array<keyof CalendarConfig> = [
        'refreshToken', 'accessToken', 'accessTokenExpiresAt',
        'connectedAs', 'connectedAt', 'lastFetchAt',
    ];
    const cur = (context.globalState.get(CAL_CONFIG_KEY) as Partial<CalendarConfig>) ?? {};
    const next: Partial<CalendarConfig> = { ...cur };
    let dirty = false;
    for (const k of globalKeys) {
        if (k in patch) {
            (next as any)[k] = (patch as any)[k];
            dirty = true;
        }
    }
    if (dirty) await context.globalState.update(CAL_CONFIG_KEY, next);
}

/** 회사 디렉토리 내부 캐시 파일 경로. workspace 없으면 globalStorage 로 fallback. */
function _cachePath(context: vscode.ExtensionContext): string {
    const ws = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
    if (ws) return path.join(ws, '.astra', 'company', '_shared', 'calendar_cache.md');
    return path.join(context.globalStorageUri.fsPath, 'company', '_shared', 'calendar_cache.md');
}

export interface RefreshResult {
    ok: boolean;
    count: number;
    error?: string;
    cachePath: string;
}

/**
 * iCal URL 에서 fetch → ICS 파싱 → upcoming 필터 → 마크다운 캐시 파일에 쓰기.
 * URL 비어있으면 즉시 ok:false 반환 (사용자 안내는 호출자가).
 */
export async function refreshCalendarCache(context: vscode.ExtensionContext): Promise<RefreshResult> {
    const cfg = readCalendarConfig(context);
    const cachePath = _cachePath(context);
    if (!cfg.icalUrl) {
        return { ok: false, count: 0, error: 'iCal URL 이 설정되지 않았습니다. 명령 팔레트에서 "Astra: Google Calendar 연결" 을 먼저 실행하세요.', cachePath };
    }
    if (!/^https?:\/\//.test(cfg.icalUrl)) {
        return { ok: false, count: 0, error: 'URL 이 http:// 또는 https:// 로 시작하지 않습니다.', cachePath };
    }

    let raw: string;
    try {
        // Node 18+ 의 native fetch 사용 — axios / node-fetch 의존성 없이.
        const res = await fetch(cfg.icalUrl, {
            method: 'GET',
            headers: { 'User-Agent': 'Astra-Calendar/1.0' },
            signal: AbortSignal.timeout(15000),
        });
        if (!res.ok) {
            return { ok: false, count: 0, error: `HTTP ${res.status} — URL 이 잘못됐거나 만료됐을 수 있습니다.`, cachePath };
        }
        raw = await res.text();
    } catch (e: any) {
        return { ok: false, count: 0, error: `다운로드 실패: ${e?.message ?? String(e)}`, cachePath };
    }

    const events = parseIcs(raw);
    const upcoming = selectUpcoming(events, cfg.daysAhead);

    const now = new Date();
    const md = _renderMarkdown(upcoming, cfg.daysAhead, now);
    try {
        fs.mkdirSync(path.dirname(cachePath), { recursive: true });
        fs.writeFileSync(cachePath, md, 'utf8');
        // 구조화 JSON 캐시 — 충돌 감지(conflictCheck)가 사용. md 와 같은 시점·같은 범위.
        const structured = upcoming.map((e) => ({
            summary: e.summary,
            startIso: e.start.toISOString(),
            endIso: e.end ? e.end.toISOString() : undefined,
            allDay: e.allDay,
            location: e.location || undefined,
        }));
        fs.writeFileSync(_eventsJsonPath(cachePath), JSON.stringify(structured, null, 2), 'utf8');
    } catch (e: any) {
        return { ok: false, count: 0, error: `캐시 저장 실패: ${e?.message ?? String(e)}`, cachePath };
    }

    await writeCalendarConfig(context, { lastFetchAt: now.toISOString() });
    return { ok: true, count: upcoming.length, cachePath };
}

/** Agent prompt 에 주입할 캐시 본문 읽기. 없으면 빈 문자열. */
export function readCalendarCache(context: vscode.ExtensionContext): string {
    const cachePath = _cachePath(context);
    try {
        if (!fs.existsSync(cachePath)) return '';
        return fs.readFileSync(cachePath, 'utf8');
    } catch {
        return '';
    }
}

function _eventsJsonPath(mdCachePath: string): string {
    return mdCachePath.replace(/\.md$/, '.json');
}

/**
 * 구조화 이벤트 캐시 읽기 — 충돌 감지용. 캐시 없음/깨짐 → 빈 배열
 * (충돌 검사가 일정 생성을 막는 false-positive 를 내지 않도록 보수적).
 */
export function readCalendarEventsCache(context: vscode.ExtensionContext): Array<{
    summary: string; startIso: string; endIso?: string; allDay: boolean; location?: string;
}> {
    try {
        const file = _eventsJsonPath(_cachePath(context));
        if (!fs.existsSync(file)) return [];
        const arr = JSON.parse(fs.readFileSync(file, 'utf8'));
        return Array.isArray(arr) ? arr.filter((e: any) => e && typeof e.startIso === 'string') : [];
    } catch {
        return [];
    }
}

function _renderMarkdown(events: IcsEvent[], daysAhead: number, now: Date): string {
    const tsLabel = (d: Date, allDay: boolean) => {
        const yy = d.getFullYear(), mm = String(d.getMonth() + 1).padStart(2, '0'), dd = String(d.getDate()).padStart(2, '0');
        const wk = ['일', '월', '화', '수', '목', '금', '토'][d.getDay()];
        if (allDay) return `${yy}-${mm}-${dd} (${wk})`;
        const HH = String(d.getHours()).padStart(2, '0'), MM = String(d.getMinutes()).padStart(2, '0');
        return `${yy}-${mm}-${dd} (${wk}) ${HH}:${MM}`;
    };
    const lines: string[] = [
        '# 📅 다가오는 일정 (Google Calendar)',
        `_업데이트: ${tsLabel(now, false)} · 향후 ${daysAhead}일_`,
        '',
    ];
    if (events.length === 0) {
        lines.push('_없음_');
    } else {
        for (const ev of events) {
            const ts = tsLabel(ev.start, ev.allDay);
            const loc = ev.location ? ` — 📍 ${ev.location}` : '';
            lines.push(`- **${ts}** · ${ev.summary}${loc}`);
        }
    }
    return lines.join('\n') + '\n';
}
