import { AUDIT_BIZ_SIGN_HEADER, createAuditCorrelationId, resolveAuditBizSign } from '#/api/onlineDev/auditHeaders';

const MAX_FIELDS = 50;
const MAX_FIELD_NAME_LENGTH = 64;
const MAX_VALUE_LENGTH = 400;
const MAX_TITLE_LENGTH = 400;
const PENDING_CONTEXT_TTL = 5 * 60 * 1000;
export const AUDIT_DISPLAY_FIELDS_HEADER = 'X-Audit-Display-Fields';
export { AUDIT_BIZ_SIGN_HEADER };

export interface AuditDisplayField {
  displayValue: string;
  field: string;
}

let pendingAuditContext: undefined | { correlationId: string; expiresAt: number; fields: AuditDisplayField[] };

const REFERENCE_COMPONENTS = new Set([
  'groupSelect',
  'organizeSelect',
  'popupSelect',
  'popupTableSelect',
  'posSelect',
  'relationForm',
  'roleSelect',
  'userSelect',
  'usersSelect',
]);

const EXCLUDED_COMPONENTS = new Set(['password', 'sign', 'signature']);

function hasValue(value: any) {
  return value !== undefined && value !== null && value !== '';
}

function flattenOptions(options: any[]): any[] {
  const result: any[] = [];
  for (const option of options || []) {
    result.push(option);
    const children = option?.children;
    if (Array.isArray(children)) result.push(...flattenOptions(children));
  }
  return result;
}

function displayFromRecord(record: any, field: any): string | undefined {
  if (!record || typeof record !== 'object') return undefined;
  const labelKey = field?.relationField || field?.fieldNames?.label || field?.props?.label;
  const candidates = [labelKey, 'fullName', 'orgNameTree', 'label', 'name', 'text', 'title'];
  for (const key of candidates) {
    if (!key || !hasValue(record[key])) continue;
    return String(record[key]);
  }
  return undefined;
}

function displayFromOptions(value: any, field: any): string | undefined {
  const options = flattenOptions(Array.isArray(field?.options) ? field.options : []);
  if (!options.length) return undefined;
  const valueKey = field?.fieldNames?.value || field?.props?.value || 'value';
  const labelKey = field?.fieldNames?.label || field?.props?.label || 'label';
  const values = Array.isArray(value) ? value : [value];
  const labels = values
    .map((item) => options.find((option) => String(option?.[valueKey]) === String(item))?.[labelKey])
    .filter((item) => hasValue(item))
    .map(String);
  return labels.length ? labels.join('/') : undefined;
}

function sameValue(left: any, right: any) {
  const normalize = (value: any) => (Array.isArray(value) ? value.map(String).sort() : String(value ?? ''));
  return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
}

function resolveDisplayValue(field: any, rawValue: any, selectedValue: any, relationValue: any): string | undefined {
  const relationValueKey = field?.propsValue || field?.fieldNames?.value || 'id';
  const currentRelation = relationValue && sameValue(relationValue[relationValueKey], rawValue) ? relationValue : undefined;
  const selected = selectedValue && sameValue(selectedValue.value, rawValue) ? selectedValue.option : currentRelation;
  if (Array.isArray(selected)) {
    const values = selected
      .map((item) => displayFromRecord(item, field) ?? (typeof item === 'object' ? undefined : String(item)))
      .filter((item) => hasValue(item));
    if (values.length) return values.join('/');
  } else {
    const value = displayFromRecord(selected, field);
    if (value) return value;
  }

  const optionValue = displayFromOptions(rawValue, field);
  if (optionValue) return optionValue;

  if (REFERENCE_COMPONENTS.has(field?.__config__?.jnpfKey)) return undefined;
  if (Array.isArray(rawValue))
    return rawValue
      .filter((item) => hasValue(item))
      .map(String)
      .join('/');
  if (typeof rawValue === 'object') return undefined;
  return hasValue(rawValue) ? String(rawValue) : undefined;
}

/**
 * 收集主表中启用审计标题的字段显示值。客户端只提供“用户看到的内容”，
 * 后端仍以已发布表单配置为白名单并负责最终标题组装。
 */
export function buildAuditDisplayFields(fields: any[], formData: any, selectedValues: Record<string, any> = {}, relationData: Record<string, any> = {}) {
  const result: AuditDisplayField[] = [];

  function walk(fieldList: any[]) {
    if (!Array.isArray(fieldList) || result.length >= MAX_FIELDS) return;
    for (const field of fieldList) {
      const config = field?.__config__;
      if (!config) continue;
      const fieldName = field.__vModel__;
      if (config.biz_log_enabled === true && !config.isSubTable && fieldName && !EXCLUDED_COMPONENTS.has(config.jnpfKey)) {
        const displayValue = resolveDisplayValue(field, formData?.[fieldName], selectedValues[fieldName], relationData[fieldName]);
        if (displayValue) result.push({ field: fieldName, displayValue: displayValue.slice(0, MAX_VALUE_LENGTH) });
      }
      if (config.jnpfKey !== 'table' && Array.isArray(config.children)) walk(config.children);
      if (result.length >= MAX_FIELDS) return;
    }
  }

  walk(fields);
  return result;
}

function resolveSameOriginWritePath(url: string, method: string) {
  if (!['delete', 'patch', 'post', 'put'].includes(String(method || '').toLowerCase())) return false;
  if (typeof url !== 'string' || !url.trim() || url.startsWith('//')) return false;
  try {
    const browserOrigin = globalThis.location?.origin;
    const absolute = /^[a-z][a-z\d+.-]*:/i.test(url);
    if (absolute && !browserOrigin) return false;
    const resolved = new URL(url, browserOrigin || 'http://audit.local');
    if (browserOrigin && resolved.origin !== browserOrigin) return false;
    return resolved.pathname;
  } catch {
    return false;
  }
}

function isLimsWriteRequest(url: string, method: string) {
  const pathname = resolveSameOriginWritePath(url, method);
  return !!pathname && (pathname.startsWith('/api/lims/') || pathname.startsWith('/api/extend/'));
}

function isExcludedAuditRequest(url: string) {
  try {
    const resolved = new URL(url, globalThis.location?.origin || 'http://audit.local');
    return /\/sign\/(?:verify|init-all)\/?$/i.test(resolved.pathname);
  } catch {
    return false;
  }
}

function headerValue(headers: Record<string, any>, name: string) {
  const key = Object.keys(headers || {}).find((item) => item.toLowerCase() === name.toLowerCase());
  return key ? String(headers[key] || '').trim() : '';
}

function encodeDisplayFields(fields: AuditDisplayField[]) {
  const limited: AuditDisplayField[] = [];
  let remaining = MAX_TITLE_LENGTH;
  for (const item of fields || []) {
    if (!item?.field || !item?.displayValue || remaining <= 0 || limited.length >= MAX_FIELDS) continue;
    const displayValue = String(item.displayValue)
      .replaceAll(/[\r\n]+/g, ' ')
      .trim()
      .slice(0, remaining);
    if (!displayValue) continue;
    limited.push({ field: String(item.field).slice(0, MAX_FIELD_NAME_LENGTH), displayValue });
    remaining -= displayValue.length + 1;
  }
  if (!limited.length) return '';
  const bytes = new TextEncoder().encode(JSON.stringify(limited));
  let binary = '';
  for (const byte of bytes) binary += String.fromCodePoint(byte);
  return globalThis.btoa(binary);
}

/**
 * GlobalFormModal 的自定义 onSubmit 可能继续使用打开弹窗前闭包中的 onlineUtils。
 * 暂存当前事件的标题字段和关联键，使事件内多个请求共用同一 operationId；后续取得
 * 真实 biz_sign 时，真实签名会替换随机关联键并供该事件的后续请求继续复用。
 */
export function registerPendingAuditDisplayFields(fields: AuditDisplayField[]) {
  const normalized = Array.isArray(fields) ? fields.filter((item) => item?.field && item?.displayValue) : [];
  const correlationId = createAuditCorrelationId();
  pendingAuditContext = { correlationId, expiresAt: Date.now() + PENDING_CONTEXT_TTL, fields: normalized };
  return correlationId;
}

export function buildGlobalAuditRequestHeaders(url: string, method: string, data: any, headers: Record<string, any> = {}) {
  if (!isLimsWriteRequest(url, method) || isExcludedAuditRequest(url)) return headers;
  const context = pendingAuditContext && pendingAuditContext.expiresAt >= Date.now() ? pendingAuditContext : undefined;
  if (!context) pendingAuditContext = undefined;
  const resolvedBizSign = resolveAuditBizSign(data) || headerValue(headers, AUDIT_BIZ_SIGN_HEADER);
  if (context && resolvedBizSign) context.correlationId = resolvedBizSign;
  const bizSign = resolvedBizSign || context?.correlationId;
  if (!bizSign) return headers;

  const auditHeaders = { ...headers, [AUDIT_BIZ_SIGN_HEADER]: bizSign };
  if (context && !auditHeaders[AUDIT_DISPLAY_FIELDS_HEADER]) {
    const encoded = encodeDisplayFields(context.fields);
    if (encoded) auditHeaders[AUDIT_DISPLAY_FIELDS_HEADER] = encoded;
  }
  return auditHeaders;
}

/**
 * 为当前在线表单的自定义提交脚本创建局部 onlineUtils。
 * 只对同源 LIMS 写请求附加标题显示值，不影响 GET、平台接口和外部地址。
 */
export function createAuditRequestOnlineUtils(
  baseOnlineUtils: any,
  getAuditDisplayFields: () => AuditDisplayField[],
  getBizSign?: () => unknown,
  correlationId = createAuditCorrelationId(),
) {
  if (!baseOnlineUtils || typeof baseOnlineUtils.request !== 'function') return baseOnlineUtils;
  let activeCorrelationId = correlationId;
  return {
    ...baseOnlineUtils,
    request(url: string, method: string, data = {}, headers = {}) {
      if (!isLimsWriteRequest(url, method) || isExcludedAuditRequest(url)) return baseOnlineUtils.request(url, method, data, headers);
      const encoded = encodeDisplayFields(getAuditDisplayFields?.() || []);
      const callbackBizSign = getBizSign?.();
      const resolvedBizSign =
        resolveAuditBizSign(callbackBizSign == null ? data : { biz_sign: callbackBizSign }) || headerValue(headers, AUDIT_BIZ_SIGN_HEADER);
      if (resolvedBizSign) activeCorrelationId = resolvedBizSign;
      const bizSign = resolvedBizSign || activeCorrelationId;
      const auditHeaders = { ...headers };
      if (encoded) auditHeaders[AUDIT_DISPLAY_FIELDS_HEADER] = encoded;
      if (bizSign) auditHeaders[AUDIT_BIZ_SIGN_HEADER] = bizSign;
      return baseOnlineUtils.request(url, method, data, auditHeaders);
    },
  };
}

/** 为已完成电子签名的自定义按钮请求附加签名头，数据接口等同源写请求同样适用。 */
export function createSignedAuditRequestOnlineUtils(baseOnlineUtils: any, getBizSign: () => unknown) {
  if (!baseOnlineUtils || typeof baseOnlineUtils.request !== 'function') return baseOnlineUtils;
  return {
    ...baseOnlineUtils,
    request(url: string, method: string, data = {}, headers = {}) {
      if (!resolveSameOriginWritePath(url, method) || isExcludedAuditRequest(url)) {
        return baseOnlineUtils.request(url, method, data, headers);
      }
      const bizSign = resolveAuditBizSign({ biz_sign: getBizSign?.() });
      const auditHeaders = bizSign ? { ...headers, [AUDIT_BIZ_SIGN_HEADER]: bizSign } : headers;
      return baseOnlineUtils.request(url, method, data, auditHeaders);
    },
  };
}
