import type {
  FillDataResponse,
  FillFieldData,
  FillFieldType,
  RepeatAppendForm,
  RepeatDeleteForm,
  RepeatGroup,
  RepeatRestoreForm,
  RepeatRow,
  SourceSyncCommand,
  SourceSyncDifference,
  SourceSyncPreview,
  SourceSynchronized,
  StepFillStructure,
} from '../domain/types';

import { parseTag } from '../domain/tags';

const FIELD_TYPES = new Set<FillFieldType>(['text', 'multiline', 'select', 'checkbox', 'date']);

export class FillDataError extends Error {
  constructor(message: string, public readonly status?: number) {
    super(message);
    this.name = 'FillDataError';
  }
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

export function parseFillData(raw: unknown): FillDataResponse {
  if (!isRecord(raw) || !isRecord(raw.fields)) throw new Error('填写数据格式无效');
  const fields: Record<string, FillFieldData> = {};
  const checkboxGroupRules = new Map<string, string>();
  for (const [tag, value] of Object.entries(raw.fields)) {
    if (!tag || !isRecord(value) || !FIELD_TYPES.has(value.type as FillFieldType)) throw new Error('填写数据格式无效');
    if (value.required !== undefined && typeof value.required !== 'boolean') throw new Error('填写数据格式无效');
    if (value.readOnly !== undefined && typeof value.readOnly !== 'boolean') throw new Error('填写数据格式无效');
    if (value.valueSource !== undefined && typeof value.valueSource !== 'string') throw new Error('填写数据格式无效');
    if (value.formulaStatus !== undefined && !['ERROR', 'MISSING_INPUT', 'SUCCESS'].includes(String(value.formulaStatus))) {
      throw new Error('填写数据格式无效');
    }
    if (value.formulaMessage !== undefined && typeof value.formulaMessage !== 'string') throw new Error('填写数据格式无效');
    const type = value.type as FillFieldType;
    const parsedTag = parseTag(tag);
    const hasCheckboxGroup = value.checkboxGroupCode !== undefined;
    const hasCheckboxGroupMin = value.checkboxGroupMinSelected !== undefined;
    const hasCheckboxGroupMax = value.checkboxGroupMaxSelected !== undefined;
    if (hasCheckboxGroup && (!validIdentity(value.checkboxGroupCode)
      || type !== 'checkbox' || value.readOnly === true || parsedTag?.type === 'reference')) {
      throw new Error('填写数据格式无效');
    }
    if (!hasCheckboxGroup && (hasCheckboxGroupMin || hasCheckboxGroupMax)) throw new Error('填写数据格式无效');
    if (hasCheckboxGroup && hasCheckboxGroupMin !== hasCheckboxGroupMax) throw new Error('填写数据格式无效');
    const checkboxGroupMinSelected = hasCheckboxGroupMin ? value.checkboxGroupMinSelected : 0;
    const checkboxGroupMaxSelected = hasCheckboxGroupMax ? value.checkboxGroupMaxSelected : 1;
    if (hasCheckboxGroup && (!Number.isInteger(checkboxGroupMinSelected)
      || Number(checkboxGroupMinSelected) < 0
      || (checkboxGroupMaxSelected !== null
        && (!Number.isInteger(checkboxGroupMaxSelected)
          || Number(checkboxGroupMaxSelected) < 1
          || Number(checkboxGroupMinSelected) > Number(checkboxGroupMaxSelected))))) {
      throw new Error('填写数据格式无效');
    }
    if (hasCheckboxGroup) {
      const groupCode = value.checkboxGroupCode as string;
      const ruleIdentity = `${checkboxGroupMinSelected}:${checkboxGroupMaxSelected === null ? '*' : checkboxGroupMaxSelected}`;
      if (checkboxGroupRules.has(groupCode) && checkboxGroupRules.get(groupCode) !== ruleIdentity) {
        throw new Error('填写数据格式无效');
      }
      checkboxGroupRules.set(groupCode, ruleIdentity);
    }
    const metadata = {
      ...(hasCheckboxGroup
        ? {
            checkboxGroupCode: value.checkboxGroupCode as string,
            checkboxGroupMaxSelected: checkboxGroupMaxSelected as null | number,
            checkboxGroupMinSelected: checkboxGroupMinSelected as number,
          }
        : {}),
      ...(typeof value.formulaMessage === 'string' ? { formulaMessage: value.formulaMessage } : {}),
      ...(typeof value.formulaStatus === 'string'
        ? { formulaStatus: value.formulaStatus as FillFieldData['formulaStatus'] }
        : {}),
      ...(value.readOnly === true ? { readOnly: true } : {}),
      required: value.required === true,
      ...(typeof value.valueSource === 'string' ? { valueSource: value.valueSource } : {}),
    };
    if (type === 'checkbox') {
      if (value.value !== null && typeof value.value !== 'boolean') throw new Error('填写数据格式无效');
      fields[tag] = { ...metadata, type, value: value.value ?? false };
    } else {
      if (value.value !== null && typeof value.value !== 'string') throw new Error('填写数据格式无效');
      fields[tag] = { ...metadata, type, value: value.value as null | string };
    }
  }
  const repeatFieldGroups = new Map<string, string>();
  for (const [tag, field] of Object.entries(fields)) {
    const parsed = parseTag(tag);
    if (parsed?.type !== 'repeat') continue;
    const key = `${parsed.groupCode}\n${parsed.fieldCode}`;
    const ruleIdentity = field.checkboxGroupCode
      ? `${field.checkboxGroupCode}:${field.checkboxGroupMinSelected}:${field.checkboxGroupMaxSelected === null ? '*' : field.checkboxGroupMaxSelected}`
      : '';
    if (repeatFieldGroups.has(key)
      && repeatFieldGroups.get(key) !== ruleIdentity) throw new Error('填写数据格式无效');
    repeatFieldGroups.set(key, ruleIdentity);
  }
  const repeatGroups = raw.repeatGroups === undefined ? [] : parseRepeatGroups(raw.repeatGroups);
  const stepStructure = raw.stepStructure === undefined ? undefined : parseStepStructure(raw.stepStructure);
  const pendingSourceSync = raw.pendingSourceSync === undefined || raw.pendingSourceSync === null
    ? undefined
    : parseSourceSynchronized(raw.pendingSourceSync);
  return {
    fields,
    repeatGroups,
    ...(pendingSourceSync ? { pendingSourceSync } : {}),
    ...(stepStructure ? { stepStructure } : {}),
  };
}

function validIdentity(value: unknown): value is string {
  return typeof value === 'string' && /^[\w-]{1,80}$/u.test(value);
}

function parseRepeatRow(value: unknown, expectedGroup?: string): RepeatRow {
  if (!isRecord(value) || !validIdentity(value.groupCode) || !validIdentity(value.rowId)
    || value.rowId === 'prototype' || value.groupCode !== (expectedGroup ?? value.groupCode)
    || !Number.isInteger(value.rowOrder) || Number(value.rowOrder) < 1
    || !['ACTIVE', 'DELETE_PREPARED', 'PREPARED'].includes(String(value.status))) {
    throw new Error('填写数据格式无效');
  }
  if (value.deleteOperationCode !== undefined && value.deleteOperationCode !== null
    && typeof value.deleteOperationCode !== 'string') throw new Error('填写数据格式无效');
  if (value.deleteReason !== undefined && value.deleteReason !== null
    && typeof value.deleteReason !== 'string') throw new Error('填写数据格式无效');
  if (value.status === 'DELETE_PREPARED'
    && (!value.deleteOperationCode || !value.deleteReason)) throw new Error('填写数据格式无效');
  return value as unknown as RepeatRow;
}

function parseRepeatGroups(value: unknown): RepeatGroup[] {
  if (!Array.isArray(value)) throw new Error('填写数据格式无效');
  const groups = new Set<string>();
  return value.map((item) => {
    if (!isRecord(item) || !validIdentity(item.groupCode) || groups.has(item.groupCode)
      || !Number.isInteger(item.minRows) || !Number.isInteger(item.maxRows)
      || !Number.isInteger(item.activeRowCount)
      || Number(item.minRows) < 0 || Number(item.maxRows) < Number(item.minRows)
      || Number(item.activeRowCount) < 0 || !Array.isArray(item.rows)) {
      throw new Error('填写数据格式无效');
    }
    groups.add(item.groupCode);
    const rows = item.rows.map((row) => parseRepeatRow(row, item.groupCode as string));
    if (rows.length !== item.activeRowCount) throw new Error('填写数据格式无效');
    const checkboxGroups: Record<string, string> = {};
    if (item.checkboxGroups !== undefined) {
      if (!isRecord(item.checkboxGroups)) throw new Error('填写数据格式无效');
      for (const [fieldCode, checkboxGroupCode] of Object.entries(item.checkboxGroups)) {
        if (!validIdentity(fieldCode) || !validIdentity(checkboxGroupCode)) {
          throw new Error('填写数据格式无效');
        }
        checkboxGroups[fieldCode] = checkboxGroupCode;
      }
    }
    return { ...item, checkboxGroups, rows } as unknown as RepeatGroup;
  });
}

function parseStepStructure(value: unknown): StepFillStructure {
  if (!isRecord(value) || !isRecord(value.configuration)
    || typeof value.configuration.singleStep !== 'boolean'
    || !Array.isArray(value.configuration.steps)) throw new Error('填写数据格式无效');
  const stepIds = new Set<string>();
  const steps = value.configuration.steps.map((step) => {
    if (!isRecord(step) || !validIdentity(step.id) || stepIds.has(step.id)
      || typeof step.name !== 'string' || !step.name.trim()
      || !Number.isInteger(step.order) || Number(step.order) < 1) throw new Error('填写数据格式无效');
    stepIds.add(step.id);
    return {
      id: step.id,
      name: step.name,
      order: Number(step.order),
    };
  });
  if (steps.length < 1 || steps.length > 20
    || (value.configuration.singleStep && steps.length !== 1)) throw new Error('填写数据格式无效');
  const parseAssignments = (assignments: unknown, repeatGroups: boolean): Record<string, string> => {
    if (!isRecord(assignments)) throw new Error('填写数据格式无效');
    const result: Record<string, string> = {};
    for (const [key, stepId] of Object.entries(assignments)) {
      if ((!key || (repeatGroups && !validIdentity(key)))
        || !validIdentity(stepId) || !stepIds.has(stepId)) throw new Error('填写数据格式无效');
      result[key] = stepId;
    }
    return result;
  };
  return {
    configuration: { singleStep: value.configuration.singleStep, steps },
    controlSteps: parseAssignments(value.controlSteps, false),
    repeatGroupSteps: parseAssignments(value.repeatGroupSteps, true),
  };
}

function messageFor(status: number): string {
  if (status === 401) return '填写凭证已过期，请重新打开文档';
  if (status === 403) return '无权读取填写数据';
  if (status === 404) return '填写数据不存在';
  return '填写数据加载失败';
}

function parseSourceSyncDifference(value: unknown): SourceSyncDifference {
  const optionalStrings = [
    value && typeof value === 'object' ? (value as Record<string, unknown>).sourceFetchedAt : undefined,
    value && typeof value === 'object' ? (value as Record<string, unknown>).sourceFieldVersionId : undefined,
    value && typeof value === 'object' ? (value as Record<string, unknown>).sourceRecordId : undefined,
    value && typeof value === 'object' ? (value as Record<string, unknown>).sourceRevisionId : undefined,
    value && typeof value === 'object' ? (value as Record<string, unknown>).sourceValueId : undefined,
  ];
  if (!isRecord(value)
    || typeof value.bindingId !== 'string' || !value.bindingId
    || typeof value.fieldCode !== 'string' || !value.fieldCode
    || typeof value.fieldName !== 'string'
    || typeof value.dataType !== 'string' || !value.dataType
    || !['CROSS_TEMPLATE', 'LIMS_TASK'].includes(String(value.sourceType))
    || (value.sourceProperty !== undefined && value.sourceProperty !== null && typeof value.sourceProperty !== 'string')
    || (value.sourceType === 'LIMS_TASK' && (typeof value.sourceProperty !== 'string' || !value.sourceProperty))
    || typeof value.changed !== 'boolean'
    || optionalStrings.some((item) => item !== undefined && item !== null && typeof item !== 'string')
    || (value.previousValue !== undefined && value.previousValue !== null && typeof value.previousValue !== 'string')
    || (value.currentValue !== undefined && value.currentValue !== null && typeof value.currentValue !== 'string')) {
    throw new FillDataError('来源快照同步响应无效');
  }
  return value as unknown as SourceSyncDifference;
}

function sourceSyncBase(value: unknown) {
  if (!isRecord(value)
    || typeof value.recordId !== 'string' || !value.recordId
    || typeof value.revisionId !== 'string' || !value.revisionId
    || !Number.isInteger(value.recordVersion) || Number(value.recordVersion) < 0
    || !Number.isInteger(value.changedCount) || Number(value.changedCount) < 0
    || !Array.isArray(value.differences)) {
    throw new FillDataError('来源快照同步响应无效');
  }
  return {
    changedCount: Number(value.changedCount),
    differences: value.differences.map(parseSourceSyncDifference),
    recordId: value.recordId,
    recordVersion: Number(value.recordVersion),
    revisionId: value.revisionId,
  };
}

function parseSourceSyncPreview(value: unknown): SourceSyncPreview {
  const base = sourceSyncBase(value);
  if (!isRecord(value) || typeof value.previewToken !== 'string' || !value.previewToken) {
    throw new FillDataError('来源快照同步响应无效');
  }
  return { ...base, previewToken: value.previewToken };
}

function parseSourceSynchronized(value: unknown): SourceSynchronized {
  const base = sourceSyncBase(value);
  if (!isRecord(value) || typeof value.operationCode !== 'string' || !value.operationCode
    || !Number.isInteger(value.recalculatedCount) || Number(value.recalculatedCount) < 0
    || !Array.isArray(value.changedFieldCodes)
    || value.changedFieldCodes.some((fieldCode) => typeof fieldCode !== 'string' || !fieldCode)) {
    throw new FillDataError('来源快照同步响应无效');
  }
  return {
    ...base,
    changedFieldCodes: [...new Set(value.changedFieldCodes as string[])],
    operationCode: value.operationCode,
    recalculatedCount: Number(value.recalculatedCount),
  };
}

async function sourceSyncRequest(
  context: { apiBaseUrl: string; ticket: string },
  path: string,
  body?: SourceSyncCommand,
): Promise<unknown> {
  let response: Response;
  try {
    response = await fetch(`${context.apiBaseUrl}/api/eln/onlyoffice/recordFill/sourceSync${path}`, {
      method: 'POST',
      headers: {
        Authorization: `Ticket ${context.ticket}`,
        ...(body ? { 'Content-Type': 'application/json' } : {}),
      },
      ...(body ? { body: JSON.stringify(body) } : {}),
    });
  } catch {
    throw new FillDataError('来源快照同步失败');
  }
  if (!response.ok) throw new FillDataError(
    response.status === 401 ? '填写凭证已过期，请重新打开文档' : '来源快照同步失败',
    response.status,
  );
  try { return await response.json(); } catch { throw new FillDataError('来源快照同步响应无效'); }
}

export async function previewSourceSync(
  context: { apiBaseUrl: string; ticket: string },
): Promise<SourceSyncPreview> {
  return parseSourceSyncPreview(await sourceSyncRequest(context, '/preview'));
}

export async function synchronizeSource(
  context: { apiBaseUrl: string; ticket: string }, command: SourceSyncCommand,
): Promise<SourceSynchronized> {
  return parseSourceSynchronized(await sourceSyncRequest(context, '', command));
}

export async function fetchFillData(context: { apiBaseUrl: string; ticket: string }): Promise<FillDataResponse> {
  let response: Response;
  try {
    response = await fetch(`${context.apiBaseUrl}/api/eln/onlyoffice/recordFill/data`, {
      headers: { Authorization: `Ticket ${context.ticket}` },
    });
  } catch {
    throw new FillDataError('填写数据加载失败');
  }
  if (!response.ok) throw new FillDataError(messageFor(response.status), response.status);
  try {
    return parseFillData(await response.json());
  } catch (error) {
    if (error instanceof FillDataError) throw error;
    throw new FillDataError('填写数据格式无效');
  }
}

export async function appendRepeatRow(
  context: { apiBaseUrl: string; ticket: string },
  form: RepeatAppendForm,
): Promise<RepeatRow> {
  let response: Response;
  try {
    response = await fetch(`${context.apiBaseUrl}/api/eln/onlyoffice/recordFill/repeatRows`, {
      method: 'POST',
      headers: { Authorization: `Ticket ${context.ticket}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(form),
    });
  } catch {
    throw new FillDataError('新增重复行失败');
  }
  if (!response.ok) throw new FillDataError(messageFor(response.status), response.status);
  let value: unknown;
  try { value = await response.json(); } catch { throw new FillDataError('新增重复行响应无效'); }
  if (!isRecord(value) || typeof value.groupCode !== 'string' || typeof value.rowId !== 'string'
    || typeof value.rowOrder !== 'number' || !['ACTIVE', 'PREPARED'].includes(String(value.status))) {
    throw new FillDataError('新增重复行响应无效');
  }
  return value as unknown as RepeatRow;
}

async function changeRepeatRow(
  context: { apiBaseUrl: string; ticket: string },
  action: 'delete' | 'restore',
  form: RepeatDeleteForm | RepeatRestoreForm,
): Promise<RepeatRow> {
  let response: Response;
  try {
    response = await fetch(`${context.apiBaseUrl}/api/eln/onlyoffice/recordFill/repeatRows/${action}`, {
      method: 'POST',
      headers: { Authorization: `Ticket ${context.ticket}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(form),
    });
  } catch {
    throw new FillDataError(action === 'delete' ? '删除重复行失败' : '恢复重复行失败');
  }
  if (!response.ok) throw new FillDataError(messageFor(response.status), response.status);
  let value: unknown;
  try { value = await response.json(); } catch { throw new FillDataError('重复行响应无效'); }
  try { return parseRepeatRow(value); } catch { throw new FillDataError('重复行响应无效'); }
}

export function deleteRepeatRow(
  context: { apiBaseUrl: string; ticket: string }, form: RepeatDeleteForm,
): Promise<RepeatRow> {
  return changeRepeatRow(context, 'delete', form);
}

export function restoreRepeatRow(
  context: { apiBaseUrl: string; ticket: string }, form: RepeatRestoreForm,
): Promise<RepeatRow> {
  return changeRepeatRow(context, 'restore', form);
}
