import { createCachedLoader, loadDropdownBundle } from "@/services/dropdownCache";
import { getAuthToken } from "@/utils/token";
import {
  NONE_SELECT_VALUE,
  type ReferenceBasesRecord,
  type ReferenceQuery,
  type ReferenceRecord,
  type ReferenceSetPageOptions,
  type ReferenceSetQuery,
  type ReferenceSetRecord,
  type SelectOption,
} from "./types";

interface BrapiPagination {
  currentPage: number;
  pageSize: number;
  totalCount: number;
  totalPages: number;
}

interface BrapiListResponse<T> {
  metadata: {
    pagination: BrapiPagination;
    status: Array<Record<string, unknown>>;
    datafiles: Array<Record<string, unknown>>;
  };
  result: {
    data: T[];
  };
}

interface BrapiSingleResponse<T> {
  metadata: {
    pagination: BrapiPagination;
    status: Array<Record<string, unknown>>;
    datafiles: Array<Record<string, unknown>>;
  };
  result: T;
}

interface VariantSetResponse {
  variantSetDbId: string;
  variantSetName: string | null;
  referenceSetDbId: string | null;
}

interface VariantResponse {
  variantDbId: string;
  referenceSetDbId: string | null;
}

type ReferenceSetPayload = Partial<Record<
  | "id"
  | "reference_set_name"
  | "assembly_pui"
  | "description"
  | "is_derived"
  | "md5checksum"
  | "source_uri"
  | "species_ontology_term"
  | "species_ontology_termuri"
  | "source_germplasm_id",
  unknown
>>;

type ReferencePayload = Partial<Record<
  "id" | "reference_name" | "reference_set_id" | "length" | "md5checksum" | "source_divergence",
  unknown
>>;

type ReferenceBasesPayload = Partial<Record<
  "id" | "reference_id" | "page_number" | "bases",
  unknown
>>;

const URL_PATTERN = /^https?:\/\/.+/i;
const BASES_PATTERN = /^[ACGTNacgtn*.-]*$/;

const apiBase = () => {
  if (typeof window !== "undefined") return "";
  return process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
};

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  const token = getAuthToken();
  const response = await fetch(`${apiBase()}${path}`, {
    ...init,
    headers: {
      "Content-Type": "application/json",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(init?.headers || {}),
    },
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `请求失败：${response.status}`);
  }
  return response.json() as Promise<T>;
}

const optionalText = (value: unknown) => {
  const normalized = String(value ?? "").trim();
  if (!normalized || normalized === NONE_SELECT_VALUE) return null;
  return normalized;
};

const requiredText = (value: unknown, message: string) => {
  const normalized = optionalText(value);
  if (!normalized) throw new Error(message);
  return normalized;
};

const optionalNumber = (value: unknown) => {
  const normalized = optionalText(value);
  if (normalized === null) return null;
  const parsed = Number(normalized);
  return Number.isNaN(parsed) ? null : parsed;
};

const optionalBoolean = (value: unknown) => {
  const normalized = optionalText(value);
  if (!normalized) return null;
  return ["true", "1", "yes"].includes(normalized.toLowerCase());
};

const optionalUrl = (value: unknown, label: string) => {
  const normalized = optionalText(value);
  if (!normalized) return null;
  if (!URL_PATTERN.test(normalized)) {
    throw new Error(`${label} 必须是有效的 URL`);
  }
  return normalized;
};

const validateBases = (value: unknown, required = false) => {
  const normalized = optionalText(value);
  if (!normalized) {
    if (required) throw new Error("碱基序列片段不能为空");
    return null;
  }
  if (normalized.length > 2048) {
    throw new Error("碱基序列片段不能超过 2048 字符");
  }
  if (!BASES_PATTERN.test(normalized)) {
    throw new Error("碱基序列仅允�?A/C/G/T/N 及常见占位符");
  }
  return normalized.toUpperCase();
};

export const mapReferenceSet = (item: ReferenceSetRecord): ReferenceSetRecord => ({
  ...item,
  id: item.referenceSetDbId || item.id,
  reference_set_name: item.reference_set_name || item.referenceSetName || null,
  assembly_pui: item.assembly_pui || item.assemblyPUI || null,
  is_derived: item.is_derived ?? item.isDerived ?? null,
  source_uri: item.source_uri || item.sourceURI || null,
  species_ontology_term:
    item.species_ontology_term
    || item.speciesOntologyTerm
    || (item as { species?: { term?: string } }).species?.term
    || null,
  species_ontology_termuri:
    item.species_ontology_termuri
    || item.speciesOntologyTermURI
    || (item as { species?: { termURI?: string } }).species?.termURI
    || null,
  source_germplasm_id:
    item.source_germplasm_id
    || item.sourceGermplasmDbId
    || (item as { sourceGermplasm?: Array<{ germplasmDbId?: string }> }).sourceGermplasm?.[0]?.germplasmDbId
    || null,
  source_germplasm_name:
    item.source_germplasm_name
    || item.sourceGermplasmName
    || (item as { sourceGermplasm?: Array<{ germplasmName?: string }> }).sourceGermplasm?.[0]?.germplasmName
    || null,
});

export const mapReference = (reference: ReferenceRecord): ReferenceRecord => ({
  ...reference,
  id: reference.referenceDbId || reference.id,
  reference_name: reference.reference_name || reference.referenceName || null,
  reference_set_id: reference.reference_set_id || reference.referenceSetDbId || null,
  reference_set_name: reference.reference_set_name || reference.referenceSetName || null,
  source_divergence: reference.source_divergence ?? reference.sourceDivergence ?? null,
});

export const mapReferenceBases = (item: ReferenceBasesRecord): ReferenceBasesRecord => ({
  ...item,
  id: item.referenceBasesDbId || item.id,
  reference_id: item.reference_id || item.referenceDbId || null,
  reference_name: item.reference_name || item.referenceName || null,
  page_number: item.page_number ?? item.pageNumber ?? null,
});

function buildSpecies(payload: ReferenceSetPayload) {
  const term = optionalText(payload.species_ontology_term);
  const termURI = optionalUrl(payload.species_ontology_termuri, "物种本体 URI");
  if (!term && !termURI) return undefined;
  return { term, termURI };
}

function buildReferenceSetWriteBody(payload: ReferenceSetPayload) {
  const body: Record<string, unknown> = {
    referenceSetName: requiredText(payload.reference_set_name, "请填�?ReferenceSet 名称"),
  };
  const optionalFields: Array<[string, unknown]> = [
    ["assemblyPUI", optionalText(payload.assembly_pui)],
    ["description", optionalText(payload.description)],
    ["isDerived", optionalBoolean(payload.is_derived)],
    ["md5checksum", optionalText(payload.md5checksum)],
    ["sourceURI", optionalUrl(payload.source_uri, "来源 URI")],
    ["sourceGermplasmDbId", optionalText(payload.source_germplasm_id)],
  ];
  optionalFields.forEach(([key, value]) => {
    if (value !== null && value !== undefined) body[key] = value;
  });
  const species = buildSpecies(payload);
  if (species) body.species = species;
  return body;
}

function buildReferenceWriteBody(payload: ReferencePayload) {
  const body: Record<string, unknown> = {
    referenceName: requiredText(payload.reference_name, "请填�?Reference 名称"),
    referenceSetDbId: requiredText(payload.reference_set_id, "请选择 ReferenceSet"),
  };
  const length = optionalNumber(payload.length);
  const sourceDivergence = optionalNumber(payload.source_divergence);
  const md5checksum = optionalText(payload.md5checksum);
  if (length !== null) {
    if (length < 0) throw new Error("序列长度不能为负");
    body.length = length;
  }
  if (sourceDivergence !== null) body.sourceDivergence = sourceDivergence;
  if (md5checksum) body.md5checksum = md5checksum;
  return body;
}

function buildReferenceBasesWriteBody(payload: ReferenceBasesPayload, creating: boolean) {
  const pageNumber = optionalNumber(payload.page_number);
  if (pageNumber === null) throw new Error("请填写分页序");
  if (pageNumber < 0) throw new Error("分页序号不能为负");
  return {
    referenceDbId: requiredText(payload.reference_id, "请选择 Reference"),
    pageNumber,
    bases: validateBases(payload.bases, creating),
  };
}

function filterReferenceSets(rows: ReferenceSetRecord[], query?: ReferenceSetQuery) {
  const name = optionalText(query?.reference_set_name)?.toLowerCase();
  const assembly = optionalText(query?.assembly_pui)?.toLowerCase();
  return rows.filter((row) => {
    if (name && !String(row.reference_set_name ?? "").toLowerCase().includes(name)) return false;
    if (assembly && !String(row.assembly_pui ?? "").toLowerCase().includes(assembly)) return false;
    return true;
  });
}

function filterReferences(rows: ReferenceRecord[], query?: ReferenceQuery) {
  const name = optionalText(query?.reference_name)?.toLowerCase();
  const referenceSetId = optionalText(query?.reference_set_id);
  return rows.filter((row) => {
    if (referenceSetId && row.reference_set_id !== referenceSetId) return false;
    if (name && !String(row.reference_name ?? "").toLowerCase().includes(name)) return false;
    return true;
  });
}

function attachReferenceSetCounts(
  rows: ReferenceSetRecord[],
  references: ReferenceRecord[],
  variantSets: VariantSetResponse[],
  variants: VariantResponse[],
) {
  return rows.map((row) => ({
    ...row,
    reference_count: references.filter((item) => item.reference_set_id === row.id).length,
    variantset_count: variantSets.filter((item) => item.referenceSetDbId === row.id).length,
    variant_count: variants.filter((item) => item.referenceSetDbId === row.id).length,
  }));
}

function attachReferenceBasesStats(references: ReferenceRecord[], basesPages: ReferenceBasesRecord[]) {
  return references.map((reference) => {
    const pages = basesPages.filter((page) => page.reference_id === reference.id);
    const basesTotalLength = pages.reduce((total, page) => total + String(page.bases ?? "").length, 0);
    return {
      ...reference,
      bases_page_count: pages.length,
      bases_total_length: basesTotalLength,
    };
  });
}

function enrichReferenceSetNames(
  references: ReferenceRecord[],
  referenceSets: ReferenceSetRecord[],
): ReferenceRecord[] {
  const label = new Map(referenceSets.map((item) => [item.id, item.reference_set_name || item.id]));
  return references.map((reference) => ({
    ...reference,
    reference_set_name: reference.reference_set_id
      ? label.get(reference.reference_set_id) ?? reference.reference_set_name
      : reference.reference_set_name,
  }));
}

function enrichGermplasmNames(
  rows: ReferenceSetRecord[],
  germplasm: SelectOption[],
): ReferenceSetRecord[] {
  const label = new Map(germplasm.map((item) => [item.value, item.label]));
  return rows.map((row) => ({
    ...row,
    source_germplasm_name: row.source_germplasm_id
      ? label.get(row.source_germplasm_id) ?? row.source_germplasm_name ?? row.source_germplasm_id
      : row.source_germplasm_name,
  }));
}

const referenceSetRowsLoader = createCachedLoader(async () => {
  const response = await request<BrapiListResponse<ReferenceSetRecord>>("/brapi/v2/referencesets?page=0&pageSize=10");
  return response.result.data.map(mapReferenceSet);
});

const referenceRowsLoader = createCachedLoader(async () => {
  const response = await request<BrapiListResponse<ReferenceRecord>>("/brapi/v2/references?page=0&pageSize=10");
  return response.result.data.map(mapReference);
});

const variantSetRowsLoader = createCachedLoader(async () => {
  const response = await request<BrapiListResponse<VariantSetResponse>>("/brapi/v2/variantsets?page=0&pageSize=10");
  return response.result.data;
});

const variantRowsLoader = createCachedLoader(async () => {
  const response = await request<BrapiListResponse<VariantResponse>>("/brapi/v2/variants?page=0&pageSize=10");
  return response.result.data;
});

const referenceBasesRowsLoader = createCachedLoader(async () => {
  const response = await request<BrapiListResponse<ReferenceBasesRecord>>("/brapi/v2/referencebases?page=0&pageSize=10");
  return response.result.data.map(mapReferenceBases);
});

export function invalidateReferenceSetPageCache() {
  referenceSetRowsLoader.invalidate();
  referenceRowsLoader.invalidate();
  variantSetRowsLoader.invalidate();
  variantRowsLoader.invalidate();
  referenceBasesRowsLoader.invalidate();
}

export async function fetchReferenceSetOptions(force = false): Promise<ReferenceSetPageOptions> {
  const [sharedOptions, referenceSets, references] = await Promise.all([
    loadDropdownBundle({ germplasms: true }, force),
    referenceSetRowsLoader.load(force),
    referenceRowsLoader.load(force),
  ]);

  return {
    germplasm: sharedOptions.germplasms,
    referenceSets: referenceSets.map((item) => ({
      value: item.id,
      label: item.reference_set_name || item.id,
    })),
    references: references.map((item) => ({
      value: item.id,
      label: `${item.reference_name || item.id}${item.reference_set_name ? ` / ${item.reference_set_name}` : ""}`,
    })),
  };
}

export async function loadReferenceSetPageData(params: {
  referenceSetQuery?: ReferenceSetQuery;
  referenceQuery?: ReferenceQuery;
  force?: boolean;
} = {}) {
  const force = params.force ?? false;
  const [options, referenceSets, references, variantSets, variants, basesPages] = await Promise.all([
    fetchReferenceSetOptions(force),
    referenceSetRowsLoader.load(force),
    referenceRowsLoader.load(force),
    variantSetRowsLoader.load(force),
    variantRowsLoader.load(force),
    referenceBasesRowsLoader.load(force),
  ]);

  const enrichedSets = enrichGermplasmNames(
    attachReferenceSetCounts(referenceSets, references, variantSets, variants),
    options.germplasm,
  );
  const enrichedReferences = attachReferenceBasesStats(
    enrichReferenceSetNames(references, referenceSets),
    basesPages,
  );

  return {
    options,
    referenceSets: filterReferenceSets(enrichedSets, params.referenceSetQuery),
    references: filterReferences(enrichedReferences, params.referenceQuery),
    basesPages,
    variantSets,
    variants,
  };
}

export async function fetchReferenceSetRows(query?: ReferenceSetQuery, force = false): Promise<ReferenceSetRecord[]> {
  const { referenceSets } = await loadReferenceSetPageData({ referenceSetQuery: query, force });
  return referenceSets;
}

export async function fetchReferenceRows(query?: ReferenceQuery, force = false): Promise<ReferenceRecord[]> {
  const { references } = await loadReferenceSetPageData({ referenceQuery: query, force });
  return references;
}

export async function fetchReferenceSetDetail(id: string): Promise<ReferenceSetRecord> {
  const response = await request<BrapiSingleResponse<ReferenceSetRecord>>(
    `/brapi/v2/referencesets/${encodeURIComponent(id)}`,
  );
  const { options, references, variantSets, variants } = await loadReferenceSetPageData();
  const [detail] = enrichGermplasmNames(
    attachReferenceSetCounts([mapReferenceSet(response.result)], references, variantSets, variants),
    options.germplasm,
  );
  return detail;
}

export async function fetchReferenceDetail(id: string): Promise<ReferenceRecord> {
  const response = await request<BrapiSingleResponse<ReferenceRecord>>(
    `/brapi/v2/references/${encodeURIComponent(id)}`,
  );
  const { referenceSets, basesPages } = await loadReferenceSetPageData();
  const [detail] = attachReferenceBasesStats(
    enrichReferenceSetNames([mapReference(response.result)], referenceSets),
    basesPages,
  );
  return detail;
}

export async function fetchReferenceBasesRows(referenceDbId?: string, force = false): Promise<ReferenceBasesRecord[]> {
  const pages = await referenceBasesRowsLoader.load(force);
  const filtered = referenceDbId
    ? pages.filter((page) => page.reference_id === referenceDbId)
    : pages;
  return filtered.sort((a, b) => Number(a.page_number ?? 0) - Number(b.page_number ?? 0));
}

export function normalizeReferenceSetFormData(record: ReferenceSetRecord): Record<string, unknown> {
  return {
    id: record.id,
    reference_set_name: record.reference_set_name ?? "",
    assembly_pui: record.assembly_pui ?? "",
    description: record.description ?? "",
    is_derived: record.is_derived === true ? "true" : record.is_derived === false ? "false" : NONE_SELECT_VALUE,
    md5checksum: record.md5checksum ?? "",
    source_uri: record.source_uri ?? "",
    species_ontology_term: record.species_ontology_term ?? "",
    species_ontology_termuri: record.species_ontology_termuri ?? "",
    source_germplasm_id: record.source_germplasm_id && record.source_germplasm_id !== NONE_SELECT_VALUE
      ? record.source_germplasm_id
      : NONE_SELECT_VALUE,
  };
}

export function normalizeReferenceFormData(record: ReferenceRecord): Record<string, unknown> {
  return {
    id: record.id,
    reference_name: record.reference_name ?? "",
    reference_set_id: record.reference_set_id ?? "",
    length: record.length ?? "",
    md5checksum: record.md5checksum ?? "",
    source_divergence: record.source_divergence ?? "",
  };
}

export function normalizeReferenceBasesFormData(record: ReferenceBasesRecord): Record<string, unknown> {
  return {
    id: record.id,
    reference_id: record.reference_id ?? "",
    page_number: record.page_number ?? "",
    bases: record.bases ?? "",
  };
}

export async function createReferenceSetRow(payload: ReferenceSetPayload): Promise<ReferenceSetRecord> {
  const body = {
    ...buildReferenceSetWriteBody(payload),
    ...(optionalText(payload.id) ? { referenceSetDbId: optionalText(payload.id) } : {}),
  };
  const response = await request<BrapiListResponse<ReferenceSetRecord>>("/brapi/v2/referencesets", {
    method: "POST",
    body: JSON.stringify(body),
  });
  invalidateReferenceSetPageCache();
  return mapReferenceSet(response.result.data[0]);
}

export async function updateReferenceSetRow(id: string, payload: ReferenceSetPayload): Promise<ReferenceSetRecord> {
  const requestedId = optionalText(payload.id);
  if (requestedId && requestedId !== id) {
    throw new Error("ReferenceSet ID 不可修改，请新建记录");
  }
  const response = await request<BrapiSingleResponse<ReferenceSetRecord>>(
    `/brapi/v2/referencesets/${encodeURIComponent(id)}`,
    {
      method: "PUT",
      body: JSON.stringify(buildReferenceSetWriteBody(payload)),
    },
  );
  invalidateReferenceSetPageCache();
  return mapReferenceSet(response.result);
}

export async function deleteReferenceSetRow(id: string): Promise<void> {
  await request<BrapiSingleResponse<ReferenceSetRecord>>(
    `/brapi/v2/referencesets/${encodeURIComponent(id)}`,
    { method: "DELETE" },
  );
  invalidateReferenceSetPageCache();
}

export async function createReferenceRow(payload: ReferencePayload): Promise<ReferenceRecord> {
  const body = {
    ...buildReferenceWriteBody(payload),
    ...(optionalText(payload.id) ? { referenceDbId: optionalText(payload.id) } : {}),
  };
  const response = await request<BrapiListResponse<ReferenceRecord>>("/brapi/v2/references", {
    method: "POST",
    body: JSON.stringify(body),
  });
  invalidateReferenceSetPageCache();
  return mapReference(response.result.data[0]);
}

export async function updateReferenceRow(id: string, payload: ReferencePayload): Promise<ReferenceRecord> {
  const requestedId = optionalText(payload.id);
  if (requestedId && requestedId !== id) {
    throw new Error("Reference ID 不可修改，请新建记录");
  }
  const response = await request<BrapiSingleResponse<ReferenceRecord>>(
    `/brapi/v2/references/${encodeURIComponent(id)}`,
    {
      method: "PUT",
      body: JSON.stringify(buildReferenceWriteBody(payload)),
    },
  );
  invalidateReferenceSetPageCache();
  return mapReference(response.result);
}

export async function deleteReferenceRow(id: string): Promise<void> {
  await request<BrapiSingleResponse<ReferenceRecord>>(
    `/brapi/v2/references/${encodeURIComponent(id)}`,
    { method: "DELETE" },
  );
  invalidateReferenceSetPageCache();
}

export async function createReferenceBasesRow(payload: ReferenceBasesPayload): Promise<ReferenceBasesRecord> {
  const body = {
    ...buildReferenceBasesWriteBody(payload, true),
    ...(optionalText(payload.id) ? { referenceBasesDbId: optionalText(payload.id) } : {}),
  };
  const response = await request<BrapiListResponse<ReferenceBasesRecord>>("/brapi/v2/referencebases", {
    method: "POST",
    body: JSON.stringify(body),
  });
  invalidateReferenceSetPageCache();
  return mapReferenceBases(response.result.data[0]);
}

export async function updateReferenceBasesRow(id: string, payload: ReferenceBasesPayload): Promise<ReferenceBasesRecord> {
  const requestedId = optionalText(payload.id);
  if (requestedId && requestedId !== id) {
    throw new Error("ReferenceBases ID 不可修改，请新建记录");
  }
  const response = await request<BrapiSingleResponse<ReferenceBasesRecord>>(
    `/brapi/v2/referencebases/${encodeURIComponent(id)}`,
    {
      method: "PUT",
      body: JSON.stringify(buildReferenceBasesWriteBody(payload, false)),
    },
  );
  invalidateReferenceSetPageCache();
  return mapReferenceBases(response.result);
}

export async function deleteReferenceBasesRow(id: string): Promise<void> {
  await request<BrapiSingleResponse<ReferenceBasesRecord>>(
    `/brapi/v2/referencebases/${encodeURIComponent(id)}`,
    { method: "DELETE" },
  );
  invalidateReferenceSetPageCache();
}
