'use strict';

const entitlementCache = require('./entitlementCache');
const licenseValidator = require('./licenseValidator');
const { getSyncContext } = require('../modules/sync/sync.context');

const BILLING_MODULE_ALIASES = Object.freeze({
  billing: ['billing', 'facturacion', 'billing_autofactura', 'billing-access', 'billing_access', 'mefacturo'],
  autofactura: ['autofactura', 'billing_autofactura', 'billing-autofactura', 'mefacturo'],
  billingQr: ['billing_qr', 'billing-qr', 'billingqr', 'qrprinting', 'qr_printing', 'qr-printing', 'billing_autofactura', 'billing-print'],
  billingPrint: ['billing-print', 'billing_print', 'billing_qr', 'billing-qr', 'qrprinting', 'qr_printing', 'facturacion'],
  mefacturo: ['mefacturo', 'me-facturo', 'autofactura', 'billing_autofactura'],
});

function asObject(value) {
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
}

function asArray(value) {
  if (!value) return [];
  return Array.isArray(value) ? value : [value];
}

function firstValue(...values) {
  for (const value of values) {
    if (value !== undefined && value !== null && String(value).trim() !== '') return value;
  }
  return null;
}

function firstString(...values) {
  const value = firstValue(...values);
  return value === null ? null : String(value).trim();
}

function flag(value) {
  if (value === true) return true;
  if (typeof value === 'string') return ['1', 'true', 'yes', 'on', 'si'].includes(value.trim().toLowerCase());
  return false;
}

function flagFalse(value) {
  if (value === false) return true;
  if (typeof value === 'string') return ['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
  return false;
}

function hasFlag(object, names) {
  return names.some((name) => flag(asObject(object)[name]));
}

function hasFalseFlag(object, names) {
  return names.some((name) => flagFalse(asObject(object)[name]));
}

function normalizeName(value) {
  return String(value || '').trim().toLowerCase().replace(/\s+/g, '_');
}

function addModule(modules, value) {
  const normalized = normalizeName(value);
  if (!normalized) return;
  modules.add(normalized);
  modules.add(normalized.replace(/_/g, '-'));
  modules.add(normalized.replace(/-/g, '_'));
}

function collectModules(payload = {}) {
  const modules = new Set();
  const entitlements = asObject(payload.entitlements || payload.entitlement);
  const features = payload.features;
  const license = asObject(payload.license);

  for (const value of asArray(payload.modules)) addModule(modules, value);
  for (const value of asArray(entitlements.modules)) addModule(modules, value);
  for (const value of asArray(license.modules)) addModule(modules, value);

  if (Array.isArray(features)) {
    for (const value of features) addModule(modules, value);
  } else if (features && typeof features === 'object') {
    for (const [key, enabled] of Object.entries(features)) {
      if (enabled === true) addModule(modules, key);
    }
  }

  if (entitlements.billing_access === true || payload.billing_access === true || license.billing_access === true) {
    addModule(modules, 'billing');
    addModule(modules, 'facturacion');
    addModule(modules, 'autofactura');
    addModule(modules, 'billing_qr');
    addModule(modules, 'billing-print');
  }
  if (entitlements.billing_qr === true || payload.billing_qr === true) {
    addModule(modules, 'billing_qr');
    addModule(modules, 'billing-print');
  }
  if (features && typeof features === 'object') {
    if (features.billing === true) addModule(modules, 'billing');
    if (features.autofactura === true) addModule(modules, 'autofactura');
    if (features.qrPrinting === true || features.billingQr === true || features.billing_qr === true) addModule(modules, 'billing_qr');
    if (features.mefacturo === true) addModule(modules, 'mefacturo');
  }
  for (const source of [payload, entitlements, license]) {
    if (hasFlag(source, ['billing', 'facturacion', 'billing_access', 'billingAccess'])) {
      addModule(modules, 'billing');
      addModule(modules, 'facturacion');
    }
    if (hasFlag(source, ['autofactura', 'billing_autofactura', 'billingAutofactura'])) {
      addModule(modules, 'autofactura');
      addModule(modules, 'billing_autofactura');
    }
    if (hasFlag(source, ['billing_qr', 'billingQr', 'qrPrinting', 'qr_printing'])) {
      addModule(modules, 'billing_qr');
      addModule(modules, 'billing-print');
    }
    if (hasFlag(source, ['billing_print', 'billingPrint', 'billing-print'])) {
      addModule(modules, 'billing-print');
    }
    if (hasFlag(source, ['mefacturo', 'meFacturo', 'me_facturo'])) addModule(modules, 'mefacturo');
  }

  return modules;
}

function hasAlias(modules, aliases) {
  return aliases.some((alias) => modules.has(normalizeName(alias)) || modules.has(normalizeName(alias).replace(/_/g, '-')));
}

function normalizeEntitlements(payload = {}) {
  const modules = collectModules(payload);
  const entitlements = asObject(payload.entitlements || payload.entitlement);
  const features = payload.features && typeof payload.features === 'object' && !Array.isArray(payload.features) ? payload.features : {};
  const qrExplicitlyDisabled = hasFalseFlag(features, ['qrPrinting', 'billingQr', 'billing_qr'])
    || hasFalseFlag(entitlements, ['billing_qr', 'billingQr', 'billing_print', 'billingPrint', 'qrPrinting'])
    || hasFalseFlag(payload, ['billing_qr', 'billingQr', 'billing_print', 'billingPrint', 'qrPrinting']);
  const billing = hasFlag(entitlements, ['billing', 'facturacion', 'billing_access', 'billingAccess'])
    || hasFlag(payload, ['billing', 'facturacion', 'billing_access', 'billingAccess'])
    || hasFlag(features, ['billing'])
    || hasAlias(modules, BILLING_MODULE_ALIASES.billing);
  const autofactura = hasFlag(entitlements, ['autofactura', 'billing_autofactura', 'billingAutofactura'])
    || hasFlag(payload, ['autofactura', 'billing_autofactura', 'billingAutofactura'])
    || hasFlag(features, ['autofactura'])
    || hasAlias(modules, BILLING_MODULE_ALIASES.autofactura)
    || billing;
  const billingQr = !qrExplicitlyDisabled && (
    hasFlag(entitlements, ['billing_qr', 'billingQr', 'qrPrinting'])
    || hasFlag(payload, ['billing_qr', 'billingQr', 'qrPrinting'])
    || hasFlag(features, ['billingQr', 'billing_qr', 'qrPrinting'])
    || hasAlias(modules, BILLING_MODULE_ALIASES.billingQr)
  );
  const billingPrint = !qrExplicitlyDisabled && (
    hasFlag(entitlements, ['billing_print', 'billingPrint', 'billing-print'])
    || hasFlag(payload, ['billing_print', 'billingPrint', 'billing-print'])
    || hasFlag(features, ['billingPrint'])
    || hasAlias(modules, BILLING_MODULE_ALIASES.billingPrint)
    || billingQr
  );
  const mefacturo = hasFlag(entitlements, ['mefacturo', 'meFacturo', 'me_facturo'])
    || hasFlag(payload, ['mefacturo', 'meFacturo', 'me_facturo'])
    || hasFlag(features, ['mefacturo', 'meFacturo'])
    || hasAlias(modules, BILLING_MODULE_ALIASES.mefacturo)
    || autofactura;
  return { billing, autofactura, billingQr, billingPrint, mefacturo };
}

function normalizePlanType(...values) {
  const text = values.map((value) => String(value || '').toLowerCase()).find(Boolean) || '';
  if (/mensual|monthly|month/.test(text)) return 'monthly';
  if (/anual|annual|year/.test(text)) return 'annual';
  return null;
}

function normalizeDate(value) {
  if (!value) return null;
  const date = new Date(value);
  if (!Number.isFinite(date.getTime())) return String(value);
  return date.toISOString();
}

function licenseReasonIs(reason, pattern) {
  return pattern.test(String(reason || '').toUpperCase());
}

function statusFromPayload(payload = {}, fallbackState = null) {
  const license = asObject(payload.license);
  const reason = firstString(payload.reason, payload.error, license.reason, fallbackState?.reason, fallbackState?.online?.reason);
  const rawStatus = String(firstValue(license.status, payload.licenseState, payload.status, fallbackState?.licenseState, fallbackState?.status) || '').toLowerCase();
  const hasLicense = Boolean(payload.license || fallbackState || payload.allowed === true || payload.valid === true);
  const trial = payload.trial === true || fallbackState?.trial === true || fallbackState?.demo === true;
  const expiresAt = firstValue(license.expiresAt, license.expires_at, payload.validUntil, payload.expires_at, fallbackState?.expires_at);
  const graceUntil = firstValue(license.graceUntil, license.grace_until, payload.graceUntil, payload.grace_until, fallbackState?.graceUntil);
  const expiresAtMs = expiresAt ? new Date(expiresAt).getTime() : NaN;
  const graceUntilMs = graceUntil ? new Date(graceUntil).getTime() : NaN;
  const now = Date.now();
  const inCloudGrace = Number.isFinite(expiresAtMs) && expiresAtMs <= now && Number.isFinite(graceUntilMs) && graceUntilMs > now;

  if (!hasLicense || reason === 'entitlement_cache_missing') return { status: 'pending_license', valid: false, reason: 'LICENSE_STATE_MISSING' };
  if (trial) return { status: 'pending_license', valid: false, reason: 'LICENSE_STATE_MISSING' };
  if (licenseReasonIs(reason, /SUSPENDED|SUSPENDIDA/)) return { status: 'suspended', valid: false, reason: reason || 'LICENSE_SUSPENDED' };
  if (licenseReasonIs(reason, /EXPIRED|EXPIR|VENC/)) return { status: 'expired', valid: false, reason: reason || 'LICENSE_EXPIRED' };
  if (['suspended', 'revoked', 'cancelled', 'disabled'].includes(rawStatus)) {
    return { status: rawStatus === 'revoked' ? 'suspended' : rawStatus, valid: false, reason: reason || `LICENSE_${rawStatus.toUpperCase()}` };
  }
  if (rawStatus === 'expired') return { status: 'expired', valid: false, reason: reason || 'LICENSE_EXPIRED' };
  if (fallbackState?.gracePeriod === true || rawStatus === 'grace' || inCloudGrace) return { status: 'grace', valid: true, reason: reason || null };
  if (payload.allowed === false && !payload.license && !fallbackState?.valid) return { status: 'pending_license', valid: false, reason: reason || 'LICENSE_STATE_MISSING' };
  if (fallbackState && fallbackState.valid !== true) return { status: 'pending_license', valid: false, reason: fallbackState.reason || 'LICENSE_STATE_MISSING' };
  return { status: 'active', valid: true, reason: reason || null };
}

function extractBilling(payload = {}) {
  return asObject(payload.billing || payload.branchBilling || payload.mefacturo || payload.readiness);
}

function normalizeReadiness(payload = {}) {
  const billing = extractBilling(payload);
  const readiness = asObject(payload.readiness);
  return {
    portalEnabled: flag(firstValue(readiness.portalEnabled, readiness.portal_enabled, billing.portalEnabled, billing.portal_enabled, payload.portalEnabled, payload.portal_enabled)),
    pacConfigured: flag(firstValue(readiness.pacConfigured, readiness.pac_configured, billing.pacConfigured, billing.pac_configured, payload.pacConfigured, payload.pac_configured)),
    csdConfigured: flag(firstValue(readiness.csdConfigured, readiness.csd_configured, billing.csdConfigured, billing.csd_configured, payload.csdConfigured, payload.csd_configured)),
    billingConfigured: flag(firstValue(readiness.billingConfigured, readiness.billing_configured, billing.configured, billing.billingConfigured, billing.billing_configured, payload.billingConfigured, payload.billing_configured)),
    billingEnvironment: firstString(readiness.billingEnvironment, readiness.billing_environment, billing.billingEnvironment, billing.billing_environment, payload.billingEnvironment, payload.billing_environment),
    pacEnvironment: firstString(readiness.pacEnvironment, readiness.pac_environment, billing.pacEnvironment, billing.pac_environment, payload.pacEnvironment, payload.pac_environment),
    billingTestReady: flag(firstValue(readiness.billingTestReady, readiness.billing_test_ready, billing.billingTestReady, billing.billing_test_ready, payload.billingTestReady, payload.billing_test_ready)),
    productionBillingReady: flag(firstValue(readiness.productionBillingReady, readiness.production_billing_ready, billing.productionBillingReady, billing.production_billing_ready, payload.productionBillingReady, payload.production_billing_ready)),
    reason: firstString(readiness.reason, readiness.readinessReason, readiness.error, billing.reason, billing.readinessReason, billing.error, payload.readinessReason),
  };
}

function normalizeTenant(payload = {}, fallbackState = null) {
  const tenant = asObject(payload.tenant);
  const branch = asObject(payload.branch);
  const billing = extractBilling(payload);
  const control = asObject(payload.control);
  const micfdi = asObject(payload.micfdi);
  const runtimeContext = asObject(payload.runtimeContext);
  const syncContext = getSyncContext();
  return {
    tenantId: firstString(tenant.id, payload.tenantId, payload.tenant_id, fallbackState?.tenant_id, syncContext.tenantId),
    branchId: firstString(branch.id, payload.branchId, payload.branch_id, fallbackState?.branch_id, syncContext.branchId),
    controlNumber: firstString(control.controlNumber, control.control_number, runtimeContext.controlNumber, payload.controlNumber, payload.control_number, fallbackState?.control_number),
    branchCode: firstString(branch.code, runtimeContext.branchCode, billing.branchCode, billing.branch_code, payload.branchCode, payload.branch_code, fallbackState?.branch_code, syncContext.branchCode),
    branchSlug: firstString(branch.slug, runtimeContext.branchSlug, billing.branchSlug, billing.slug, billing.sucursalSlug, payload.branchSlug, payload.branch_slug, fallbackState?.branch_slug, syncContext.branchSlug),
    branchName: firstString(branch.name, billing.branchName, billing.branch_name, payload.branchName, payload.branch_name, fallbackState?.sucursal, syncContext.branchName),
    codigosucursal: firstString(micfdi.codigosucursal, micfdi.CODIGOSUCURSAL, runtimeContext.codigosucursal, billing.pacCodigoSucursal, fallbackState?.codigosucursal),
  };
}

function normalizeSourcePayload(payload = {}, source = 'missing', fallbackState = null) {
  const license = asObject(payload.license);
  const status = statusFromPayload(payload, fallbackState);
  const entitlements = normalizeEntitlements({
    ...payload,
    entitlements: payload.entitlements || fallbackState?.entitlements,
    features: payload.features || fallbackState?.features,
  });
  return {
    source,
    license: {
      status: status.status,
      valid: status.valid === true,
      planType: normalizePlanType(license.type, license.planType, license.plan, payload.planType, payload.plan, fallbackState?.planType, fallbackState?.plan),
      validFrom: normalizeDate(firstValue(license.startsAt, license.starts_at, payload.validFrom, payload.starts_at, fallbackState?.valid_from)),
      validUntil: normalizeDate(firstValue(license.expiresAt, license.expires_at, payload.validUntil, payload.expires_at, fallbackState?.expires_at)),
      graceUntil: normalizeDate(firstValue(license.graceUntil, license.grace_until, payload.graceUntil, payload.grace_until, fallbackState?.graceUntil)),
      source,
      reason: status.reason,
    },
    tenant: normalizeTenant(payload, fallbackState),
    entitlements,
    readiness: normalizeReadiness(payload),
    raw: {
      licensePresent: Boolean(payload.license || fallbackState),
      entitlementPresent: Boolean(payload.entitlements || payload.features || payload.allowed === true || fallbackState?.entitlements),
      cloudReadinessPresent: Boolean(payload.billing || payload.branchBilling || payload.mefacturo || payload.readiness),
    },
  };
}

function hasCloudReadinessPayload(payload = {}) {
  return Boolean(payload.billing || payload.branchBilling || payload.mefacturo || payload.readiness);
}

function localStatePayload(state) {
  if (!state) return null;
  return {
    valid: state.valid === true,
    status: state.valid === true ? 'active' : 'blocked',
    reason: state.reason || null,
    plan: state.plan || null,
    expires_at: state.expires_at || null,
    graceUntil: state.graceUntil || null,
    trial: state.trial === true,
    demo: state.demo === true,
    entitlements: state.entitlements || null,
    features: state.features || null,
    tenantId: state.tenant_id || null,
    branchId: state.branch_id || null,
    branchCode: state.branch_code || null,
    branchSlug: state.branch_slug || null,
    branchName: state.sucursal || null,
    controlNumber: state.control_number || null,
    codigosucursal: state.codigosucursal || null,
  };
}

function resolveForBillingPrint(options = {}) {
  const hasExplicitState = Object.prototype.hasOwnProperty.call(options, 'licenseState') && options.licenseState !== undefined;
  const state = hasExplicitState ? options.licenseState : licenseValidator.getLicenseState();

  if (options.entitlement) {
    return normalizeSourcePayload(options.entitlement, options.entitlementSource || 'cloud', state);
  }

  const cached = Object.prototype.hasOwnProperty.call(options, 'cachedEntitlement') && options.cachedEntitlement !== undefined
    ? options.cachedEntitlement
    : entitlementCache.resolveCachedEntitlement();

  const online = state?.online || null;
  if (state && (online?.valid === true || online?.ok === true) && (state.entitlements || state.features)) {
    const onlineResolved = normalizeSourcePayload(localStatePayload(state), 'cloud', state);
    if (onlineResolved.raw.cloudReadinessPresent === true || cached?.allowed !== true || !hasCloudReadinessPayload(cached)) {
      return onlineResolved;
    }
  }

  if (cached?.allowed === true) {
    return normalizeSourcePayload(cached, 'cache', state);
  }

  if (state) {
    return normalizeSourcePayload(localStatePayload(state), 'local', state);
  }

  return normalizeSourcePayload({ reason: 'LICENSE_STATE_MISSING' }, 'missing', null);
}

module.exports = {
  resolveForBillingPrint,
  normalizeEntitlements,
  normalizePlanType,
  BILLING_MODULE_ALIASES,
};
