import {
  createContext,
  useContext,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from 'react';
import { toast } from 'sonner';
import { detailFromApiError } from '../api/client';
import { MAX_EVIDENCE_PDF_BYTES } from '../lib/evidenceUploadLimits';
import {
  deleteApplicationEvidence,
  getApplicationEvidence,
  uploadApplicationEvidence,
} from './applicationEvidenceApi';
import { snapshotStore, fileStore } from './draftStorage';
import { putFile, getFile } from './serializers';
import {
  LS_KEY,
  emptyInitiativeDraft,
  type InitiativeDraft,
  type StoredApplicationForm,
} from './types';
import type { ApplicationFormState } from './initiativeFormTypes';
import type { InitiativeFormState } from './initiativeFormTypes';

function newDraftId(): string {
  try {
    return `DRAFT-${crypto.randomUUID()}`;
  } catch {
    return `DRAFT-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
  }
}

export interface DraftContextValue {
  draft: InitiativeDraft;
  /** Initiative `case_code` (Postgres) when the applicant dashboard has created/loaded a draft — use for MinIO evidence APIs. */
  caseId?: string;
  updateApplication: (patch: Partial<ApplicationFormState>) => Promise<void>;
  updateReport: (patch: Partial<InitiativeFormState>) => void;
  hydrateApplication: () => Promise<ApplicationFormState>;
  reset: () => Promise<void>;
  reload: () => void;
}

export const DraftCtx = createContext<DraftContextValue | null>(null);

const FILE_FIELDS = new Set([
  'textbookEvidenceFile',
  'researchEvidenceFile',
  'technicalEvidenceFile',
]);

function isFileField(k: string) {
  return FILE_FIELDS.has(k);
}

function fileFieldToServerEvidenceKind(
  field: string,
): import('./types').ServerEvidenceKind | null {
  if (field === 'researchEvidenceFile') return 'research';
  if (field === 'textbookEvidenceFile') return 'textbook';
  if (field === 'technicalEvidenceFile') return 'technical';
  return null;
}

function mergeStoredApplication(
  base: StoredApplicationForm,
  partial: Record<string, unknown> | undefined | null,
): StoredApplicationForm {
  if (!partial || typeof partial !== 'object') return base;
  const next = { ...base };
  for (const [k, v] of Object.entries(partial)) {
    if (k === 'textbookEvidenceFile' || k === 'researchEvidenceFile' || k === 'technicalEvidenceFile') {
      continue;
    }
    (next as Record<string, unknown>)[k] = v;
  }
  return next as StoredApplicationForm;
}

function mergeReport(
  base: InitiativeFormState,
  partial: Record<string, unknown> | undefined | null,
): InitiativeFormState {
  if (!partial || typeof partial !== 'object') return base;
  const eff = partial.effectiveness;
  return {
    ...base,
    ...partial,
    effectiveness:
      eff && typeof eff === 'object'
        ? { ...base.effectiveness, ...(eff as InitiativeFormState['effectiveness']) }
        : base.effectiveness,
    trialUnits: Array.isArray(partial.trialUnits) ? (partial.trialUnits as InitiativeFormState['trialUnits']) : base.trialUnits,
  };
}

export function DraftProvider({
  children,
  caseId,
  seedApplication,
  seedReport,
  preferLocalSnapshot = true,
}: {
  children: ReactNode;
  caseId?: string;
  seedApplication?: Record<string, unknown> | null;
  seedReport?: Record<string, unknown> | null;
  preferLocalSnapshot?: boolean;
}) {
  const [draft, setDraft] = useState<InitiativeDraft>(() => {
    if (!preferLocalSnapshot) return emptyInitiativeDraft(newDraftId());
    return snapshotStore.load<InitiativeDraft>(LS_KEY) ?? emptyInitiativeDraft(newDraftId());
  });

  const mergedCaseRef = useRef<string | null>(null);
  const draftRef = useRef(draft);
  useEffect(() => {
    draftRef.current = draft;
  }, [draft]);

  useEffect(() => {
    mergedCaseRef.current = null;
  }, [caseId]);

  useEffect(() => {
    if (!caseId || mergedCaseRef.current === caseId) return;
    const hasSomething =
      (seedApplication && Object.keys(seedApplication as object).length > 0) ||
      (seedReport && Object.keys(seedReport as object).length > 0);
    if (!hasSomething) return;
    mergedCaseRef.current = caseId;
    const base = emptyInitiativeDraft(newDraftId());
    setDraft(() => ({
      ...base,
      updatedAt: new Date().toISOString(),
      application: mergeStoredApplication(base.application, seedApplication ?? undefined),
      report: mergeReport(base.report, seedReport ?? undefined),
    }));
  }, [caseId, seedApplication, seedReport]);

  /**
   * After opening a server-backed draft, attach metadata for 2.1 / 2.2 evidence from MinIO so
   * the form can re-download PDFs when IndexedDB is empty.
   */
  useEffect(() => {
    if (!caseId || preferLocalSnapshot) return;
    const cid = caseId.trim();
    if (!cid) return;
    let cancelled = false;
    const t = window.setTimeout(() => {
      void (async () => {
        try {
          const ev = await getApplicationEvidence(cid);
          if (cancelled) return;
          setDraft((d) => {
            const app = { ...d.application };
            let changed = false;
            if (ev.research && !app.researchEvidenceFile) {
              app.researchEvidenceFile = {
                key: `${d.draftId}:researchEvidenceFile`,
                name: ev.research.originalName || 'minh-chung-2-1.pdf',
                size: ev.research.byteSize || 0,
                type: 'application/pdf',
                lastModified: Date.parse(ev.research.uploadedAt || '') || Date.now(),
                serverStorageKey: ev.research.storageKey,
                serverUploadedAt: ev.research.uploadedAt || undefined,
                serverKind: 'research',
              };
              changed = true;
            }
            if (ev.textbook && !app.textbookEvidenceFile) {
              app.textbookEvidenceFile = {
                key: `${d.draftId}:textbookEvidenceFile`,
                name: ev.textbook.originalName || 'minh-chung-2-2.pdf',
                size: ev.textbook.byteSize || 0,
                type: 'application/pdf',
                lastModified: Date.parse(ev.textbook.uploadedAt || '') || Date.now(),
                serverStorageKey: ev.textbook.storageKey,
                serverUploadedAt: ev.textbook.uploadedAt || undefined,
                serverKind: 'textbook',
              };
              changed = true;
            }
            if (ev.technical && !app.technicalEvidenceFile) {
              app.technicalEvidenceFile = {
                key: `${d.draftId}:technicalEvidenceFile`,
                name: ev.technical.originalName || 'minh-chung-ky-thuat.pdf',
                size: ev.technical.byteSize || 0,
                type: ev.technical.mimeType || 'application/pdf',
                lastModified: Date.parse(ev.technical.uploadedAt || '') || Date.now(),
                serverStorageKey: ev.technical.storageKey,
                serverUploadedAt: ev.technical.uploadedAt || undefined,
                serverKind: 'technical',
              };
              changed = true;
            }
            if (!changed) return d;
            return { ...d, updatedAt: new Date().toISOString(), application: app };
          });
        } catch {
          /* offline / 401 / MinIO not configured */
        }
      })();
    }, 0);
    return () => {
      cancelled = true;
      window.clearTimeout(t);
    };
  }, [caseId, preferLocalSnapshot, draft.draftId]);

  const saveTimer = useRef<number | null>(null);
  useEffect(() => {
    if (saveTimer.current) window.clearTimeout(saveTimer.current);
    saveTimer.current = window.setTimeout(() => {
      snapshotStore.save(LS_KEY, { ...draft, updatedAt: new Date().toISOString() });
    }, 400);
    return () => {
      if (saveTimer.current) window.clearTimeout(saveTimer.current);
    };
  }, [draft]);

  const updateApplication = useCallback<DraftContextValue['updateApplication']>(
    async (patch) => {
      const next: Partial<StoredApplicationForm> = {};
      const cid = caseId?.trim() || '';
      for (const [k, v] of Object.entries(patch)) {
        if (isFileField(k)) {
          if (v instanceof File || v === null) {
            const field = k as keyof StoredApplicationForm;
            const prevHandle = draftRef.current.application[field] as
              | import('./types').FileHandle
              | null
              | undefined;
            const evKind = fileFieldToServerEvidenceKind(k);

            if (v instanceof File && v.size > MAX_EVIDENCE_PDF_BYTES) {
              toast.error(
                `Tệp minh chứng vượt quá 50 MB (${(v.size / 1024 / 1024).toFixed(1)} MB). Vui lòng chọn tệp nhỏ hơn.`,
              );
              continue;
            }

            if (v === null && cid && evKind && prevHandle?.serverStorageKey) {
              try {
                await deleteApplicationEvidence(cid, evKind);
              } catch (e) {
                console.error('Delete evidence on server failed', e);
                toast.error(
                  detailFromApiError(
                    e,
                    'Không xóa được minh chứng trên máy chủ (đã gỡ bản cục bộ).',
                  ),
                );
              }
            }

            let handle = await putFile(draftRef.current.draftId, k, v as File | null);

            if (handle && v instanceof File && evKind) {
              if (!cid) {
                toast.message(
                  'Chưa có mã hồ sơ từ máy chủ — hãy chờ lưu bản nháp tự động (vài giây) rồi chọn lại tệp minh chứng để đồng bộ MinIO.',
                );
              } else {
                try {
                  const res = await uploadApplicationEvidence(cid, evKind, v);
                  handle = {
                    ...handle,
                    serverStorageKey: res.storageKey,
                    serverUploadedAt: res.uploadedAt,
                    serverKind: evKind,
                  };
                  const label = res.originalName?.trim() || v.name;
                  toast.success(
                    `Đã lưu minh chứng lên máy chủ: ${label} (${evKind === 'research' ? '2.1' : evKind === 'textbook' ? '2.2' : 'kỹ thuật'}).`,
                  );
                } catch (e) {
                  console.error('Evidence upload failed', e);
                  toast.error(
                    detailFromApiError(
                      e,
                      'Không tải được minh chứng lên máy chủ. Tệp vẫn lưu cục bộ trên trình duyệt.',
                    ),
                  );
                }
              }
            }

            (next as Record<string, unknown>)[k] = handle;
          }
        } else {
          (next as Record<string, unknown>)[k] = v;
        }
      }
      setDraft((d) => ({
        ...d,
        updatedAt: new Date().toISOString(),
        application: { ...d.application, ...next },
      }));
    },
    [caseId],
  );

  const updateReport = useCallback<DraftContextValue['updateReport']>((patch) => {
    setDraft((d) => {
      const { effectiveness: effIn, ...rest } = patch;
      return {
        ...d,
        updatedAt: new Date().toISOString(),
        report: {
          ...d.report,
          ...rest,
          effectiveness: effIn ? { ...d.report.effectiveness, ...effIn } : d.report.effectiveness,
        },
      };
    });
  }, []);

  const hydrateApplication = useCallback<DraftContextValue['hydrateApplication']>(async () => {
    const a = draftRef.current.application;
    const cid = caseId?.trim() || null;
    const [t, r, tech] = await Promise.all([
      getFile(a.textbookEvidenceFile, { caseId: cid, serverKind: 'textbook' }),
      getFile(a.researchEvidenceFile, { caseId: cid, serverKind: 'research' }),
      getFile(a.technicalEvidenceFile, { caseId: cid, serverKind: 'technical' }),
    ]);
    return {
      ...a,
      textbookEvidenceFile: t,
      researchEvidenceFile: r,
      technicalEvidenceFile: tech,
    };
  }, [caseId]);

  const reset = useCallback<DraftContextValue['reset']>(async () => {
    await fileStore.clearPrefix(`${draft.draftId}:`);
    snapshotStore.clear(LS_KEY);
    const nextId = newDraftId();
    setDraft(emptyInitiativeDraft(nextId));
  }, [draft.draftId]);

  const reload = useCallback<DraftContextValue['reload']>(() => {
    setDraft(snapshotStore.load<InitiativeDraft>(LS_KEY) ?? emptyInitiativeDraft(newDraftId()));
  }, []);

  const value = useMemo(
    () => ({
      draft,
      caseId,
      updateApplication,
      updateReport,
      hydrateApplication,
      reset,
      reload,
    }),
    [draft, caseId, updateApplication, updateReport, hydrateApplication, reset, reload],
  );

  return <DraftCtx.Provider value={value}>{children}</DraftCtx.Provider>;
}

/** Draft context accessor — throws outside a `<DraftProvider>`. */
export function useDraft(): DraftContextValue {
  const ctx = useContext(DraftCtx);
  if (!ctx) throw new Error('useDraft must be used inside <DraftProvider>');
  return ctx;
}

/** Draft context accessor — returns `null` outside a `<DraftProvider>` (no throw). */
export function useOptionalDraft(): DraftContextValue | null {
  return useContext(DraftCtx);
}
