/**
 * examRepository — the SINGLE module that talks to the /api/exam backend (spec R2.1 seam).
 *
 * All exam-marker persistence flows through here so a later dual-write / offline cache can slot
 * in without touching feature code. Mirrors the auth pattern of timetableService: take the
 * Supabase session JWT and send it as a Bearer token; the API enforces RLS as the user.
 */
import axios from 'axios';

import { API_BASE } from '../../config/apiConfig';
import { logger } from '../../debugConfig';
import { supabase } from '../../supabaseClient';
import type {
  AutoMapJobStatus,
  AutoMapResponse,
  BatchQueueResponse,
  BatchResultsResponse,
  CreateBatchPayload,
  CreateTemplatePayload,
  ExamBoundary,
  ExamQuestion,
  ExamResponseArea,
  ExamTemplate,
  ExamTemplateDetail,
  ExamTemplateLayout,
  MarkingBatch,
  MarkUpsertPayload,
  Neo4jSyncResult,
  PatchQuestionPayload,
  SpecPoint,
  TemplateReplacePayload,
  UpdateTemplateMetaPayload,
} from '../../types/exam.types';

const EXAM_BASE = `${API_BASE}/api/exam`;

async function authHeaders(): Promise<Record<string, string>> {
  const { data: { session } } = await supabase.auth.getSession();
  if (!session?.access_token) {
    throw new Error('No authentication token available');
  }
  return { Authorization: `Bearer ${session.access_token}` };
}

function newUuid(): string {
  if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
    return crypto.randomUUID();
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = Math.floor(Math.random() * 16);
    const v = c === 'x' ? r : (r % 4) + 8;
    return v.toString(16);
  });
}

function questionPayload(q: ExamQuestion, idMap?: Map<string, string>) {
  return {
    id: idMap?.get(q.id) ?? q.id,
    parent_id: q.parent_id ? (idMap?.get(q.parent_id) ?? q.parent_id) : null,
    label: q.label,
    order: q.order,
    max_marks: q.max_marks,
    answer_type: q.answer_type,
    mcq_options: q.mcq_options,
    mark_scheme: q.mark_scheme ?? {},
    is_container: q.is_container,
    spec_ref: q.spec_ref,
    bounds: q.bounds ?? null,
    page: q.page ?? null,
    source: q.source ?? 'manual',
    confirmed: q.confirmed ?? true,
    confidence: q.confidence ?? null,
    derivation: q.derivation ?? null,
  };
}

function responseAreaPayload(r: ExamResponseArea, idMap?: Map<string, string>, duplicate = false) {
  return {
    id: duplicate ? newUuid() : r.id,
    question_id: idMap?.get(r.question_id) ?? r.question_id,
    page: r.page,
    bounds: r.bounds,
    kind: r.kind,
    response_form: r.response_form,
    context_type: r.context_type ?? null,
    source: r.source,
    confirmed: r.confirmed,
    confidence: r.confidence,
    mark_subtype: r.mark_subtype ?? null,
    derivation: r.derivation ?? null,
  };
}

function boundaryPayload(b: ExamBoundary, idMap?: Map<string, string>, duplicate = false) {
  return {
    id: duplicate ? newUuid() : b.id,
    question_id: b.question_id ? (idMap?.get(b.question_id) ?? b.question_id) : null,
    label: b.label,
    page_index: b.page_index,
    y: b.y,
    bounds: b.bounds,
    source: b.source,
    confirmed: b.confirmed,
    confidence: b.confidence ?? null,
    derivation: b.derivation ?? null,
  };
}

function layoutPayload(layout: ExamTemplateLayout, duplicate = false) {
  return {
    id: duplicate ? newUuid() : layout.id,
    page_index: layout.page_index,
    role: layout.role ?? null,
    margin_left: layout.margin_left ?? null,
    margin_right: layout.margin_right ?? null,
    margin_top: layout.margin_top ?? null,
    margin_bottom: layout.margin_bottom ?? null,
    margins_enabled: layout.margins_enabled ?? true,
    source: layout.source ?? 'manual',
    confirmed: layout.confirmed ?? true,
    confidence: layout.confidence ?? null,
    derivation: layout.derivation ?? null,
    meta: layout.meta ?? {},
  };
}

async function replaceTemplate(
  templateId: string,
  detail: ExamTemplateDetail,
  meta?: UpdateTemplateMetaPayload,
  duplicateIds = false,
): Promise<ExamTemplateDetail> {
  const headers = await authHeaders();
  const idMap = new Map<string, string>();
  if (duplicateIds) {
    detail.questions.forEach((q) => idMap.set(q.id, newUuid()));
  }
  const res = await axios.put<ExamTemplateDetail>(
    `${EXAM_BASE}/templates/${templateId}`,
    {
      meta,
      questions: detail.questions.map((q) => questionPayload(q, idMap)),
      response_areas: detail.response_areas.map((r) => responseAreaPayload(r, idMap, duplicateIds)),
      boundaries: detail.boundaries.map((b) => boundaryPayload(b, idMap, duplicateIds)),
      layout: (detail.layout ?? []).map((layout) => layoutPayload(layout, duplicateIds)),
    },
    { headers },
  );
  return res.data;
}

export const examRepository = {
  async listTemplates(includeArchived = false): Promise<ExamTemplate[]> {
    const headers = await authHeaders();
    const res = await axios.get<{ templates: ExamTemplate[] }>(
      `${EXAM_BASE}/templates`,
      { headers, params: { include_archived: includeArchived } },
    );
    return res.data.templates ?? [];
  },

  async getTemplate(templateId: string): Promise<ExamTemplateDetail> {
    const headers = await authHeaders();
    const res = await axios.get<ExamTemplateDetail>(`${EXAM_BASE}/templates/${templateId}`, { headers });
    return res.data;
  },

  async autoMapTemplate(templateId: string): Promise<AutoMapResponse> {
    const headers = await authHeaders();
    const res = await axios.post<AutoMapResponse>(`${EXAM_BASE}/templates/${templateId}/auto-map`, {}, { headers });
    return res.data;
  },

  async getAutoMapStatus(templateId: string, jobId: string): Promise<AutoMapJobStatus> {
    const headers = await authHeaders();
    const res = await axios.get<AutoMapJobStatus>(`${EXAM_BASE}/templates/${templateId}/auto-map/${jobId}/status`, { headers });
    return res.data;
  },

  async getTemplateSourcePdf(templateId: string): Promise<ArrayBuffer> {
    const headers = await authHeaders();
    const res = await axios.get<ArrayBuffer>(`${EXAM_BASE}/templates/${templateId}/source-pdf`, {
      headers,
      responseType: 'arraybuffer',
    });
    return res.data;
  },

  async createTemplate(payload: CreateTemplatePayload): Promise<ExamTemplate> {
    const headers = await authHeaders();
    const res = await axios.post<ExamTemplate>(`${EXAM_BASE}/templates`, payload, { headers });
    return res.data;
  },

  async updateTemplateMeta(templateId: string, meta: UpdateTemplateMetaPayload): Promise<ExamTemplate> {
    const headers = await authHeaders();
    const res = await axios.patch<ExamTemplate>(`${EXAM_BASE}/templates/${templateId}`, meta, { headers });
    return res.data;
  },

  async duplicateTemplate(templateId: string, title: string): Promise<ExamTemplateDetail> {
    const detail = await this.getTemplate(templateId);
    const created = await this.createTemplate({
      title,
      subject: detail.subject ?? undefined,
      exam_id: detail.exam_id ?? undefined,
      exam_code: detail.exam_code ?? undefined,
      source_file_id: detail.source_file_id ?? undefined,
      page_count: detail.page_count ?? undefined,
      institute_id: detail.institute_id,
    });
    try {
      return await replaceTemplate(created.id, { ...detail, id: created.id }, { title, status: 'draft' }, true);
    } catch (error) {
      try {
        await this.archiveTemplate(created.id);
      } catch (archiveError) {
        logger.error('cc-exam-marker', 'Failed to archive incomplete duplicate template', {
          templateId: created.id,
          message: archiveError instanceof Error ? archiveError.message : String(archiveError),
        });
      }
      throw error;
    }
  },


  async replaceTemplate(templateId: string, payload: TemplateReplacePayload): Promise<ExamTemplateDetail> {
    const headers = await authHeaders();
    const res = await axios.put<ExamTemplateDetail>(`${EXAM_BASE}/templates/${templateId}`, payload, { headers });
    return res.data;
  },
  async archiveTemplate(templateId: string): Promise<void> {
    const headers = await authHeaders();
    await axios.delete(`${EXAM_BASE}/templates/${templateId}`, { headers });
  },

  async patchQuestion(questionId: string, payload: PatchQuestionPayload) {
    const headers = await authHeaders();
    const res = await axios.patch(`${EXAM_BASE}/questions/${questionId}`, payload, { headers });
    return res.data;
  },

  async listSpecPoints(specCode: string, search?: string): Promise<SpecPoint[]> {
    const headers = await authHeaders();
    const res = await axios.get<{ points?: SpecPoint[] } | SpecPoint[]>(
      `${EXAM_BASE}/specs/${encodeURIComponent(specCode)}/points`,
      { headers, params: search ? { q: search } : undefined },
    );
    if (Array.isArray(res.data)) return res.data;
    return res.data.points ?? [];
  },

  async syncTemplateToGraph(templateId: string): Promise<Neo4jSyncResult> {
    const headers = await authHeaders();
    const res = await axios.post<Neo4jSyncResult>(`${EXAM_BASE}/templates/${templateId}/neo4j-sync`, {}, { headers });
    return res.data;
  },

  async createBatch(payload: CreateBatchPayload): Promise<MarkingBatch> {
    const headers = await authHeaders();
    const res = await axios.post<MarkingBatch>(`${EXAM_BASE}/batches`, payload, { headers });
    return res.data;
  },

  async listBatches(params: { includeArchived?: boolean; templateId?: string } = {}): Promise<MarkingBatch[]> {
    const headers = await authHeaders();
    const res = await axios.get<{ batches: MarkingBatch[] }>(`${EXAM_BASE}/batches`, {
      headers,
      params: {
        include_archived: params.includeArchived ?? false,
        template_id: params.templateId,
      },
    });
    return res.data.batches ?? [];
  },

  async getBatchQueue(batchId: string): Promise<BatchQueueResponse> {
    const headers = await authHeaders();
    const res = await axios.get<BatchQueueResponse>(`${EXAM_BASE}/batches/${batchId}/queue`, { headers });
    return res.data;
  },

  async getBatchResults(batchId: string): Promise<BatchResultsResponse> {
    const headers = await authHeaders();
    const res = await axios.get<BatchResultsResponse>(`${EXAM_BASE}/batches/${batchId}/results`, { headers });
    return res.data;
  },

  async getBatchCsv(batchId: string): Promise<string> {
    const headers = await authHeaders();
    const res = await axios.get<string>(`${EXAM_BASE}/batches/${batchId}/csv`, { headers, responseType: 'text' });
    return res.data;
  },

  async upsertMark(markId: string, payload: MarkUpsertPayload): Promise<unknown> {
    const headers = await authHeaders();
    const res = await axios.put(`${EXAM_BASE}/marks/${markId}`, payload, { headers });
    return res.data;
  },
};

export default examRepository;
