import { API_BASE_URL, AUTH_STORAGE_KEY, getAuthToken, getSatLoginUrl } from './apiConfig.js';

const API_BASE = API_BASE_URL;
const AUTH_KEY = AUTH_STORAGE_KEY;

/** Avoid multiple full-page redirects when several API calls return 401 in parallel. */
let authRedirectScheduled = false;

async function request(path, options = {}) {
  const token = getAuthToken();
  const headers = { ...(options.headers || {}) };

  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }

  const res = await fetch(`${API_BASE}${path}`, {
    ...options,
    headers
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));

    if (res.status === 401) {
      localStorage.removeItem(AUTH_KEY);
      if (!authRedirectScheduled) {
        authRedirectScheduled = true;
        // Login page (same app family as master dashboard), not the MD home route.
        window.location.replace(getSatLoginUrl());
      }
      throw new Error('Session expired. Redirecting to sign in…');
    }

    const detail =
      typeof err.details === 'string'
        ? err.details
        : err.details != null
          ? JSON.stringify(err.details)
          : '';
    const msg = [err.message || `Request failed (${res.status})`, detail].filter(Boolean).join(' — ');
    throw new Error(msg);
  }

  return res.json();
}

function get(path) {
  return request(path, { cache: 'no-store' });
}

function post(path, body) {
  return request(path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
}

function put(path, body) {
  return request(path, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
}

function del(path) {
  return request(path, { method: 'DELETE' });
}

function postFormData(path, formData) {
  return request(path, { method: 'POST', body: formData });
}

// --- Helpers ---

function decodeBufferField(obj, field) {
  const val = obj[field];
  if (val && val.type === 'Buffer' && Array.isArray(val.data)) {
    obj[field] = String.fromCharCode(...val.data);
  }
  return obj;
}

/** List API may return `participants_json` as parsed JSON, a string, or (Node) Buffer. */
function normalizeParticipantsJsonField(raw) {
  if (raw == null) return null;
  if (Array.isArray(raw)) return raw;
  if (typeof raw === 'string') {
    try {
      const p = JSON.parse(raw);
      return Array.isArray(p) ? p : null;
    } catch {
      return null;
    }
  }
  if (typeof raw === 'object' && raw.type === 'Buffer' && Array.isArray(raw.data)) {
    try {
      const p = JSON.parse(String.fromCharCode(...raw.data));
      return Array.isArray(p) ? p : null;
    } catch {
      return null;
    }
  }
  return null;
}

function normalizeAssessment(a) {
  const o = decodeBufferField({ ...a }, 'video');
  const parsed = normalizeParticipantsJsonField(o.participants_json);
  if (parsed != null) {
    o.participants_json = parsed;
  }
  return o;
}

// --- Assessments ---

export function fetchAssessments() {
  return get('/api/orac/orac_assessments').then((rows) =>
    Array.isArray(rows) ? rows.map(normalizeAssessment) : rows
  );
}

export function fetchUserAssessments(userId) {
  return get(`/api/orac/orac_assessments?uploader_user_id=${userId}`).then((rows) =>
    Array.isArray(rows) ? rows.map(normalizeAssessment) : rows
  );
}

/**
 * Lists assessments where the current user is executor (JWT; `assign_role` selects queue).
 * userId is ignored by the API for executor/verifier JWT — kept for call-site compatibility.
 */
export function fetchAssessmentsForExecutor(_userId) {
  return get('/api/orac/orac_assessments?assign_role=executor').then((rows) =>
    Array.isArray(rows) ? rows.map(normalizeAssessment) : rows
  );
}

export function fetchAssessmentsForVerifier(_userId) {
  return get('/api/orac/orac_assessments?assign_role=verifier').then((rows) =>
    Array.isArray(rows) ? rows.map(normalizeAssessment) : rows
  );
}

/**
 * Single row as returned by the list endpoint (includes `orac_form_id`: COALESCE of assigned `form_id`, else MIN form for location).
 * Use this to refresh an assessment object passed from UI where the generic GET by id omits `orac_form_id`.
 * Executor snap flow uses the explicit `form_id` column (executor must assign via assignAssessmentForm when admin omits it).
 */
export function fetchAssessmentListProjection(assessmentId) {
  if (assessmentId == null || assessmentId === '') return Promise.resolve(null);
  const want = Number(assessmentId);
  return get(`/api/orac/orac_assessments?assessment_id=${encodeURIComponent(assessmentId)}`).then((rows) => {
    if (!Array.isArray(rows) || rows.length === 0) return null;
    const match = rows.find((r) => Number(r.assessment_id) === want);
    return match != null ? normalizeAssessment(match) : null;
  });
}

export function fetchAssessmentStatuses() {
  return get('/api/orac/orac_assessments_status/');
}

let executorDashboardInflight = null;

/** Single load for executor home: canonical statuses + executor + verifier assignment rows. */
export function fetchExecutorDashboardData() {
  if (executorDashboardInflight) {
    return executorDashboardInflight;
  }
  executorDashboardInflight = (async () => {
    const [statuses, executorAssessments, verifierAssessments] = await Promise.all([
      fetchAssessmentStatuses().catch(() => []),
      fetchAssessmentsForExecutor(),
      fetchAssessmentsForVerifier()
    ]);

    return {
      statuses: Array.isArray(statuses) ? statuses : [],
      executorAssessments: Array.isArray(executorAssessments) ? executorAssessments : [],
      verifierAssessments: Array.isArray(verifierAssessments) ? verifierAssessments : []
    };
  })().finally(() => {
    executorDashboardInflight = null;
  });

  return executorDashboardInflight;
}

export function assignExecutor(assessmentId, executorId, formId) {
  const body = { executor_id: executorId };
  if (formId != null && formId !== '') {
    body.form_id = formId;
  }
  return post(`/api/orac/orac_assessments/${assessmentId}/assign_executor`, body);
}

export function assignVerifier(assessmentId, verifierId, formId) {
  const body = { verifier_id: verifierId };
  if (formId != null && formId !== '') {
    body.form_id = formId;
  }
  return post(`/api/orac/orac_assessments/${assessmentId}/assign_verifier`, body);
}

/** Assigned executor (or admin) sets which ORAC form applies to this assessment. */
export function assignAssessmentForm(assessmentId, formId) {
  return post(`/api/orac/orac_assessments/${assessmentId}/assign_form`, {
    form_id: formId
  });
}

/** Verifier marks the risk report / verification step complete (updates assessment status). */
export function completeVerifierAssessment(assessmentId) {
  return post(`/api/orac/orac_assessments/${assessmentId}/verifier_complete`, {});
}

/**
 * Verifier sends work back to the executor: inserts `orac_verification_flow` and updates status.
 * @param {string} [returnedFor] - optional note shown to the executor
 */
export function returnAssessmentToExecutor(assessmentId, returnedFor = '') {
  return post(`/api/orac/orac_assessments/${assessmentId}/verifier_return`, {
    returned_for: returnedFor
  });
}

/** History rows for an assessment (`GET` list with filter). */
export function fetchVerificationFlowsForAssessment(assessmentId) {
  return get(`/api/orac/orac_verification_flow/?assessment_id=${encodeURIComponent(assessmentId)}`);
}

export function addParticipant(assessmentId, participantUserId) {
  return post('/api/orac/orac_participants/', {
    assessment_id: assessmentId,
    participant_user_id: participantUserId
  });
}

export function fetchParticipants(assessmentId) {
  const q = encodeURIComponent(String(assessmentId));
  return get(`/api/orac/orac_participants/?assessment_id=${q}`).then((body) => {
    if (Array.isArray(body)) return body;
    return [];
  });
}

export function uploadAssessment(formData) {
  return postFormData('/api/orac/orac_assessments/upload', formData);
}

// --- Admin-only: users, locations ---

export function fetchUsers() {
  return get('/api/orac/users/');
}

/** Admin: role catalog (`roles` table). */
export function fetchRoles() {
  return get('/api/orac/roles/');
}

/** Admin: user ↔ role links (`user_role` table). */
export function fetchUserRoles() {
  return get('/api/orac/user_role/');
}

export function fetchLocations() {
  return get('/api/orac/locations/');
}

/** Read-only country list for filters (authenticated). */
export function fetchCountries() {
  return get('/api/orac/lookup/countries').then((rows) => (Array.isArray(rows) ? rows : []));
}

export function fetchDepartments() {
  return get('/api/orac/departments/');
}

export function fetchLocationDepartments(locationId) {
  const qs = locationId ? `?location_id=${locationId}` : '';
  return get(`/api/orac/location_departments/${qs}`);
}

/** Read-only: resolve form_id for a location (`GET .../orac_form_section_map/by-location/:id`). */
export function fetchFormIdForLocation(locationId) {
  if (locationId == null || locationId === '') {
    return Promise.resolve({ form_id: null });
  }
  return get(`/api/orac/orac_form_section_map/by-location/${encodeURIComponent(locationId)}`);
}

/** Default ORAC form: `MIN(form_id)` on `orac_form_section_map` (all mapped sections load for that form). */
export function fetchDefaultOracFormId() {
  return get('/api/orac/orac_form_section_map/default-form-id').then((r) => {
    const raw = r?.form_id;
    if (raw == null || raw === '') return { form_id: null };
    const n = Number(raw);
    return { form_id: Number.isFinite(n) && n > 0 ? n : null };
  });
}

/**
 * Full ORAC form: sections + questions + options (`GET /api/orac_form_section_map/form/:form_id`).
 */
export function fetchFormWithSectionsAndQuestions(formId) {
  return get(`/api/orac/orac_form_section_map/form/${encodeURIComponent(formId)}`);
}

/** Section + mapped questions + options (`GET /api/orac_section_question_map/section/:section_id`). */
export function fetchSectionWithQuestions(sectionId) {
  return get(`/api/orac/orac_section_question_map/section/${encodeURIComponent(sectionId)}`);
}

/** All ORAC section rows (`GET /api/orac_section_details/`) — used to pick a section by title in the snap form. */
export function fetchOracSectionDetailsList() {
  return get('/api/orac/orac_section_details/').then((rows) => (Array.isArray(rows) ? rows : []));
}

/**
 * RPN master rows from `orac_likelihood_of_harm` / `orac_severity_of_harm` (SAT-style `/api/orac/rpn/*`).
 */
export function fetchRpnLikelihood() {
  return get('/api/orac/rpn/likelihood').then((rows) => (Array.isArray(rows) ? rows : []));
}

export function fetchRpnSeverity() {
  return get('/api/orac/rpn/severity').then((rows) => (Array.isArray(rows) ? rows : []));
}

export function createRpnLikelihoodRow(body) {
  return post('/api/orac/rpn/likelihood', body);
}

export function updateRpnLikelihoodRow(body) {
  return put('/api/orac/rpn/likelihood', body);
}

export function deleteRpnLikelihoodRow(likelihoodOfHarm) {
  return del(`/api/orac/rpn/likelihood/${encodeURIComponent(likelihoodOfHarm)}`);
}

export function createRpnSeverityRow(body) {
  return post('/api/orac/rpn/severity', body);
}

export function updateRpnSeverityRow(body) {
  return put('/api/orac/rpn/severity', body);
}

export function deleteRpnSeverityRow(severityOfHarm) {
  return del(`/api/orac/rpn/severity/${encodeURIComponent(severityOfHarm)}`);
}

/**
 * Create `orac_submissions` + `orac_answers` (`POST /api/orac_submissions`).
 * @param {object} p - { assessment_id, snap_id, form_id?, answers: [{ question_id, answer_value?, is_media? }] } — omit `form_id` to let the API infer it from `question_id`s.
 */
export function createOracSubmission(p) {
  return post('/api/orac/orac_submissions', p);
}

// --- Snaps (orac_snaps) — requireAuth on backend (user + executor/verifier JWTs) ---

export function fetchObservations(assessmentId) {
  const id = Number(assessmentId);
  if (!Number.isFinite(id) || id <= 0) {
    return Promise.resolve([]);
  }
  return get(`/api/orac/orac_snaps/?assessment_id=${encodeURIComponent(String(id))}`).then((body) => {
    if (Array.isArray(body)) return body;
    if (body && Array.isArray(body.data)) return body.data;
    return [];
  });
}

/**
 * Submit a snap with a captured frame (maps to `orac_snaps`: snap longblob, snap_timing metadata).
 * @param {object} payload - { assessment_id, snap_image (base64 dataURL), risk_violation_time, marks, risk_rows?, form_id?, form_answers?, extra_sections?, section_instances?, submitted_at? }
 */
export function submitSnapObservation(payload) {
  const {
    assessment_id,
    snap_image,
    risk_violation_time,
    marks,
    risk_rows,
    submitted_at,
    form_id,
    form_answers,
    extra_sections,
    section_instances
  } = payload;
  let b64 = '';
  if (typeof snap_image === 'string' && snap_image.startsWith('data:')) {
    const i = snap_image.indexOf(',');
    b64 = i >= 0 ? snap_image.slice(i + 1) : snap_image;
  } else if (typeof snap_image === 'string') {
    b64 = snap_image;
  }
  const timing = {
    risk_violation_time: risk_violation_time ?? null,
    marks: marks ?? null
  };
  if (risk_rows != null) timing.risk_rows = risk_rows;
  if (submitted_at != null) timing.submitted_at = submitted_at;
  if (form_id != null && form_id !== undefined) timing.form_id = form_id;
  if (form_answers != null && typeof form_answers === 'object') timing.form_answers = form_answers;
  if (Array.isArray(extra_sections) && extra_sections.length) timing.extra_sections = extra_sections;
  if (Array.isArray(section_instances) && section_instances.length) timing.section_instances = section_instances;
  const snap_timing = JSON.stringify(timing);
  return post('/api/orac/orac_snaps/', {
    assessment_id,
    snap: b64,
    snap_timing
  });
}

/** Update existing snap row (composite PK). Body typically `{ snap_timing: string }`. */
export function updateOracSnap(snapId, assessmentId, body) {
  return put(`/api/orac/orac_snaps/${snapId}/${assessmentId}`, body);
}

/** Delete snap and related submissions/answers (composite PK). */
export function deleteOracSnap(snapId, assessmentId) {
  const sid = encodeURIComponent(snapId);
  const aid = encodeURIComponent(assessmentId);
  return del(`/api/orac/orac_snaps/${sid}/${aid}`);
}

/**
 * Executor sends the assessment to the verifier (status → verification required).
 * Requires at least one saved snap row.
 */
export function executorSubmitForVerification(assessmentId) {
  return post(
    `/api/orac/orac_assessments/${encodeURIComponent(assessmentId)}/executor_submit_for_verification`,
    {}
  );
}

/**
 * Fetch single assessment by id (full detail with video field).
 */
export function fetchAssessmentById(assessmentId) {
  return get(`/api/orac/orac_assessments/${assessmentId}`).then(normalizeAssessment);
}

// --- ORAC form builder (admin) — uses generic CRUD + mapping endpoints ---

/**
 * @param {{ leaf_only?: boolean, with_version?: boolean }} [opts]
 * - leaf_only: only non-archived leaf forms (current version per family) — use for pickers / new audits.
 * - with_version: adds `version`, `version_root_id`, `is_archived`, `has_child_version` (server-side;
 *   you can also filter client-side with `formAllowedForNewUse` from `utils/oracFormDetails.js`).
 */
export function fetchOracFormDetailsList(opts = {}) {
  const q = new URLSearchParams();
  if (opts.leaf_only) q.set('leaf_only', '1');
  if (opts.with_version) q.set('with_version', '1');
  const suffix = q.toString() ? `?${q.toString()}` : '';
  return get(`/api/orac/orac_form_details/${suffix}`).then((rows) => (Array.isArray(rows) ? rows : []));
}

export function createOracFormDetail(body) {
  return post('/api/orac/orac_form_details/', body);
}

/** Update `orac_form_details` row (e.g. title, description, form_parent_id). */
export function updateOracFormDetail(formId, body) {
  return put(`/api/orac/orac_form_details/${encodeURIComponent(formId)}`, body);
}

/** New `orac_form_details` row with `form_parent_id` = formId; copies form–section map. */
export function forkOracFormVersion(formId, body = {}) {
  return post(`/api/orac/orac_form_details/${encodeURIComponent(formId)}/fork`, body);
}

export function deleteOracFormDetail(formId) {
  return del(`/api/orac/orac_form_details/${encodeURIComponent(formId)}`);
}

export function createOracSectionDetail(body) {
  return post('/api/orac/orac_section_details/', body);
}

export function deleteOracSectionDetail(sectionId) {
  return del(`/api/orac/orac_section_details/${encodeURIComponent(sectionId)}`);
}

export function fetchOracQuestionsList() {
  return get('/api/orac/orac_questions/').then((rows) => (Array.isArray(rows) ? rows : []));
}

/** Create a question; omit `question_id` so the DB can assign it. */
export function createOracQuestion(body) {
  return post('/api/orac/orac_questions/', body);
}

export function deleteOracQuestion(questionId, questionTypeId) {
  return del(
    `/api/orac/orac_questions/${encodeURIComponent(questionId)}/${encodeURIComponent(questionTypeId)}`
  );
}

export function fetchOracQuestionTypes() {
  return get('/api/orac/orac_question_type/').then((rows) => (Array.isArray(rows) ? rows : []));
}

export function fetchOracQuestionOptionsByQuestionId(questionId) {
  return get(`/api/orac/orac_question_options/?question_id=${encodeURIComponent(questionId)}`).then((rows) =>
    Array.isArray(rows) ? rows : []
  );
}

export function createOracQuestionOption(body) {
  return post('/api/orac/orac_question_options/', body);
}

export function deleteOracQuestionOption(questionOptionsId, questionId) {
  return del(
    `/api/orac/orac_question_options/${encodeURIComponent(questionOptionsId)}/${encodeURIComponent(questionId)}`
  );
}

/** Countries list for filters (`GET /api/lookup/countries`). */
export function fetchLookupCountries() {
  return get('/api/orac/lookup/countries').then((rows) => (Array.isArray(rows) ? rows : []));
}

/**
 * Location rows (`GET /api/locations/`). Optional filters, e.g. `{ country_id: 1 }`.
 */
export function fetchLocationsList(filters = {}) {
  const q = new URLSearchParams();
  Object.entries(filters).forEach(([k, v]) => {
    if (v != null && v !== '') q.set(k, String(v));
  });
  const qs = q.toString();
  return get(qs ? `/api/orac/locations/?${qs}` : '/api/orac/locations/').then((rows) => (Array.isArray(rows) ? rows : []));
}

/** Rows linking a form to locations (`orac_location_forms_details`). */
export function fetchOracLocationFormsDetailsByFormId(formId) {
  const id = Number(formId);
  if (!Number.isInteger(id) || id <= 0) return Promise.resolve([]);
  return get(`/api/orac/orac_location_forms_details/?form_id=${encodeURIComponent(id)}`).then((rows) =>
    Array.isArray(rows) ? rows : []
  );
}

/** Forms mapped to a location (filter assignment dropdown when the location has explicit mappings). */
export function fetchOracLocationFormsDetailsByLocationId(locationId) {
  const id = Number(locationId);
  if (!Number.isInteger(id) || id <= 0) return Promise.resolve([]);
  return get(`/api/orac/orac_location_forms_details/?location_id=${encodeURIComponent(id)}`).then((rows) =>
    Array.isArray(rows) ? rows : []
  );
}

export function mapLocationToForm(formId, locationId) {
  return post('/api/orac/orac_location_forms_details/', { form_id: formId, location_id: locationId });
}

export function unmapLocationFromForm(formId, locationId) {
  return del(
    `/api/orac/orac_location_forms_details/${encodeURIComponent(formId)}/${encodeURIComponent(locationId)}`
  );
}

export function mapSectionToForm(formId, sectionId) {
  return post('/api/orac/orac_form_section_map/', { form_id: formId, section_id: sectionId });
}

export function unmapSectionFromForm(formId, sectionId) {
  return del(
    `/api/orac/orac_form_section_map/${encodeURIComponent(formId)}/${encodeURIComponent(sectionId)}`
  );
}

export function mapQuestionToSection(sectionId, questionId) {
  return post('/api/orac/orac_section_question_map/', { section_id: sectionId, question_id: questionId });
}

/** Rows where `main_question_id` matches (RPN parent → L/S children). */
export function fetchCombinedQuestionByMain(mainQuestionId) {
  const id = Number(mainQuestionId);
  if (!Number.isInteger(id) || id <= 0) return Promise.resolve([]);
  return get(
    `/api/orac/orac_combined_question/?main_question_id=${encodeURIComponent(String(id))}`
  ).then((rows) => (Array.isArray(rows) ? rows : []));
}

export function unmapQuestionFromSection(sectionId, questionId) {
  return del(
    `/api/orac/orac_section_question_map/${encodeURIComponent(sectionId)}/${encodeURIComponent(questionId)}`
  );
}
