export type Portal = 'student' | 'tenant-admin' | 'platform-admin';

export interface AppEnv {
  portal: Portal;
  apiBaseUrl: string;
  supabaseUrl: string;
  supabasePublishableKey: string;
  tenantCode: string;
}

export interface RuntimeConfigInput {
  portal?: string;
  apiBaseUrl?: string;
  supabaseUrl?: string;
  supabasePublishableKey?: string;
  tenantCode?: string;
  TARO_APP_PORTAL?: string;
  TARO_APP_API_BASE_URL?: string;
  TARO_APP_SUPABASE_URL?: string;
  TARO_APP_SUPABASE_PUBLISHABLE_KEY?: string;
  TARO_APP_TENANT_CODE?: string;
}

declare const process: {
  env: Record<string, string | undefined>;
};

const forbiddenFrontendKeys = [
  'SUPABASE_SERVICE_ROLE_KEY',
  'SUPABASE_SECRET_KEY',
  'DATABASE_URL',
  'ALIYUN_OSS_ACCESS_KEY_SECRET',
  'TENCENT_COS_SECRET_KEY',
  'WECHAT_PAY_PRIVATE_KEY',
  'ALIPAY_APP_PRIVATE_KEY',
  'AUTH_SESSION_SECRET',
  'PLATFORM_ADMIN_API_KEY',
] as const;

const allowedRuntimeConfigKeys = [
  'portal',
  'apiBaseUrl',
  'supabaseUrl',
  'supabasePublishableKey',
  'tenantCode',
  'TARO_APP_PORTAL',
  'TARO_APP_API_BASE_URL',
  'TARO_APP_SUPABASE_URL',
  'TARO_APP_SUPABASE_PUBLISHABLE_KEY',
  'TARO_APP_TENANT_CODE',
] as const;

function normalizeString(value: unknown) {
  return typeof value === 'string' ? value.trim() : '';
}

function normalizePortal(value: unknown): Portal | null {
  if (value === 'student' || value === 'tenant-admin' || value === 'platform-admin') return value;
  return null;
}

function assertNoForbiddenKeys(input: Record<string, unknown>, source: string) {
  const leaked = forbiddenFrontendKeys.filter(key => Object.prototype.hasOwnProperty.call(input, key));
  if (leaked.length) {
    throw new Error(`Forbidden secret key in ${source}: ${leaked.join(', ')}`);
  }
  const allowed = new Set<string>(allowedRuntimeConfigKeys);
  const unknown = Object.keys(input).filter(key => !allowed.has(key));
  if (unknown.length) {
    throw new Error(`Unknown key in ${source}: ${unknown.join(', ')}`);
  }
}

export const appEnv: AppEnv = {
  portal: (process.env.TARO_APP_PORTAL || 'student') as Portal,
  apiBaseUrl: process.env.TARO_APP_API_BASE_URL || 'http://127.0.0.1:8787',
  supabaseUrl: process.env.TARO_APP_SUPABASE_URL || '',
  supabasePublishableKey: process.env.TARO_APP_SUPABASE_PUBLISHABLE_KEY || '',
  tenantCode: process.env.TARO_APP_TENANT_CODE || '',
};

let runtimeConfigPromise: Promise<AppEnv> | null = null;

export function applyRuntimeConfig(input: RuntimeConfigInput, source = 'runtime config') {
  assertNoForbiddenKeys(input as Record<string, unknown>, source);

  const portal = normalizePortal(input.portal || input.TARO_APP_PORTAL);
  if (portal) appEnv.portal = portal;

  const apiBaseUrl = normalizeString(input.apiBaseUrl || input.TARO_APP_API_BASE_URL);
  if (apiBaseUrl) appEnv.apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');

  const supabaseUrl = normalizeString(input.supabaseUrl || input.TARO_APP_SUPABASE_URL);
  if (supabaseUrl) appEnv.supabaseUrl = supabaseUrl.replace(/\/+$/, '');

  const supabasePublishableKey = normalizeString(input.supabasePublishableKey || input.TARO_APP_SUPABASE_PUBLISHABLE_KEY);
  if (supabasePublishableKey) appEnv.supabasePublishableKey = supabasePublishableKey;

  const tenantCode = normalizeString(input.tenantCode || input.TARO_APP_TENANT_CODE);
  if (tenantCode) appEnv.tenantCode = tenantCode;

  return appEnv;
}

export async function loadRuntimeConfig() {
  if (process.env.TARO_ENV !== 'h5' || typeof window === 'undefined' || typeof window.fetch !== 'function') {
    return appEnv;
  }

  const runtimeConfigUrl = `${window.location.origin}/runtime-config.json`;
  let response: Response;
  try {
    response = await window.fetch(runtimeConfigUrl, {
      cache: 'no-store',
      credentials: 'same-origin',
    });
  } catch {
    return appEnv;
  }
  if (!response.ok) return appEnv;

  const text = (await response.text()).trim();
  if (!text || !text.startsWith('{')) return appEnv;

  let config: RuntimeConfigInput;
  try {
    config = JSON.parse(text) as RuntimeConfigInput;
  } catch (error) {
    throw new Error(`Invalid Taro runtime-config.json: ${(error as Error).message}`);
  }

  return applyRuntimeConfig(config, 'runtime-config.json');
}

export function ensureRuntimeConfigLoaded() {
  if (!runtimeConfigPromise) runtimeConfigPromise = loadRuntimeConfig();
  return runtimeConfigPromise;
}

export function assertFrontendSecretsAreAbsent() {
  const leaked = forbiddenFrontendKeys.filter(key => process.env[key]);
  if (leaked.length) {
    throw new Error(`Forbidden secret env in Taro build: ${leaked.join(', ')}`);
  }
}
