import fs from 'fs';
import path from 'path';
import jwt from 'jsonwebtoken';
import { NormalizedStorePurchase, VerifyPurchaseInput } from './billing.types';
import { isCreditProduct, isKnownBillingProduct, isSubscriptionProduct } from './billingProducts';

const APPLE_PRODUCTION_BASE = 'https://api.storekit.itunes.apple.com';
const APPLE_SANDBOX_BASE = 'https://api.storekit-sandbox.itunes.apple.com';
const APPLE_SUBSCRIPTION_STATUS_ACTIVE = 1;
const APPLE_SUBSCRIPTION_STATUS_BILLING_RETRY = 3;
const APPLE_SUBSCRIPTION_STATUS_BILLING_GRACE = 4;

function base64UrlDecode(input: string): string {
  const normalized = input.replace(/-/g, '+').replace(/_/g, '/');
  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
  return Buffer.from(padded, 'base64').toString('utf8');
}

function createAppleServerJwt(): string {
  const issuerId = String(process.env.APPLE_ISSUER_ID || '').trim();
  const keyId = String(process.env.APPLE_KEY_ID || process.env.APPLE_STORE_KEY || '').trim();
  const privateKeyInline = String(process.env.APPLE_PRIVATE_KEY || '').replace(/\\n/g, '\n').trim();
  const bundleId = String(process.env.APPLE_BUNDLE_ID || '').trim();
  const keyDir = String(process.env.APPLE_KEY_DIR || path.join(process.cwd(), 'keys')).trim();

  function resolvePrivateKey(): string {
    if (privateKeyInline) {
      return privateKeyInline;
    }

    const explicitPath = String(process.env.APPLE_PRIVATE_KEY_PATH || '').trim();
    const candidates: string[] = [];
    if (explicitPath) {
      candidates.push(explicitPath);
    }
    if (keyId) {
      candidates.push(path.join(keyDir, `AuthKey_${keyId}.p8`));
      candidates.push(path.join(keyDir, `SubscriptionKey_${keyId}.p8`));
    }

    for (const filePath of candidates) {
      if (filePath && fs.existsSync(filePath)) {
        const content = fs.readFileSync(filePath, 'utf8').trim();
        if (content) return content;
      }
    }

    if (fs.existsSync(keyDir)) {
      const firstP8 = fs
        .readdirSync(keyDir)
        .filter((f) => f.endsWith('.p8'))
        .sort()[0];
      if (firstP8) {
        const content = fs.readFileSync(path.join(keyDir, firstP8), 'utf8').trim();
        if (content) return content;
      }
    }

    return '';
  }
  const privateKey = resolvePrivateKey();

  if (!issuerId || !keyId || !privateKey) {
    throw new Error(
      'Apple server verification credentials are missing (APPLE_ISSUER_ID + APPLE_KEY_ID/APPLE_STORE_KEY + APPLE_PRIVATE_KEY or key file)',
    );
  }

  const now = Math.floor(Date.now() / 1000);
  const payload: Record<string, unknown> = {
    iss: issuerId,
    iat: now,
    exp: now + 60 * 5,
    aud: 'appstoreconnect-v1',
  };
  if (bundleId) {
    payload.bid = bundleId;
  }

  return jwt.sign(payload, privateKey, {
    algorithm: 'ES256',
    keyid: keyId,
  });
}

function parseAppleDateMs(input: unknown): Date | undefined {
  const n = Number(input);
  if (!Number.isFinite(n) || n <= 0) return undefined;
  return new Date(n);
}

async function fetchAppleTransactionInfo(transactionId: string) {
  const token = createAppleServerJwt();
  const paths = [
    `${APPLE_PRODUCTION_BASE}/inApps/v1/transactions/${encodeURIComponent(transactionId)}`,
    `${APPLE_SANDBOX_BASE}/inApps/v1/transactions/${encodeURIComponent(transactionId)}`,
  ];

  let lastError = '';
  for (const url of paths) {
    const response = await fetch(url, {
      method: 'GET',
      headers: { Authorization: `Bearer ${token}` },
    });

    if (response.ok) {
      return await response.json();
    }

    lastError = `${response.status} ${await response.text()}`;
  }

  throw new Error(`Apple transaction verification failed: ${lastError}`);
}

async function fetchAppleSubscriptionStatus(anyTransactionId: string, preferredEnvironment?: string) {
  const token = createAppleServerJwt();
  const env = String(preferredEnvironment || '').toLowerCase();
  const isSandboxPreferred = env.includes('sandbox');
  const orderedBases = isSandboxPreferred
    ? [APPLE_SANDBOX_BASE, APPLE_PRODUCTION_BASE]
    : [APPLE_PRODUCTION_BASE, APPLE_SANDBOX_BASE];
  const paths = orderedBases.map(
    (base) => `${base}/inApps/v1/subscriptions/${encodeURIComponent(anyTransactionId)}`,
  );

  let lastError = '';
  for (const url of paths) {
    const response = await fetch(url, {
      method: 'GET',
      headers: { Authorization: `Bearer ${token}` },
    });

    if (response.ok) {
      return await response.json();
    }

    lastError = `${response.status} ${await response.text()}`;
  }

  throw new Error(`Apple subscription status lookup failed: ${lastError}`);
}

function decodeSignedTransactionPayload(signedTransactionInfo: string): Record<string, any> {
  const pieces = String(signedTransactionInfo || '').split('.');
  if (pieces.length < 2) {
    throw new Error('Invalid Apple signedTransactionInfo');
  }

  const payloadJson = base64UrlDecode(pieces[1]);
  return JSON.parse(payloadJson);
}

function looksLikeJws(value: unknown): boolean {
  const token = String(value || '').trim();
  if (!token) return false;
  const parts = token.split('.');
  return parts.length === 3 && parts.every((part) => part.length > 0);
}

function extractSignedTransactionFromInput(
  input: VerifyPurchaseInput,
  payload: Record<string, any>,
): { signedTransactionInfo: string; transactionPayload: Record<string, any> } | null {
  const candidates = [
    payload?.signedTransactionInfo,
    payload?.signedTransactionInfoIOS,
    payload?.jwsRepresentation,
    payload?.transactionReceipt,
    payload?.receipt,
    input.receipt,
    input.purchaseToken,
    input.transactionId,
  ];

  for (const candidate of candidates) {
    const token = String(candidate || '').trim();
    if (!looksLikeJws(token)) {
      continue;
    }
    try {
      const transactionPayload = decodeSignedTransactionPayload(token);
      if (transactionPayload && typeof transactionPayload === 'object') {
        return { signedTransactionInfo: token, transactionPayload };
      }
    } catch {
      // Continue trying other possible fields.
    }
  }

  return null;
}

function firstNonEmptyString(values: unknown[]): string {
  for (const value of values) {
    const parsed = String(value || '').trim();
    if (parsed) {
      return parsed;
    }
  }
  return '';
}

function pickLatestSubscriptionTransactionPayload(
  statusResponse: Record<string, any>,
  fallbackPayload: Record<string, any>,
): { payload: Record<string, any>; statusCode?: number } {
  const groups = Array.isArray(statusResponse?.data) ? statusResponse.data : [];
  const candidates: Array<{ payload: Record<string, any>; statusCode?: number }> = [];

  for (const group of groups) {
    const txs = Array.isArray(group?.lastTransactions) ? group.lastTransactions : [];
    for (const tx of txs) {
      const signedTransactionInfo = String(tx?.signedTransactionInfo || '').trim();
      if (!signedTransactionInfo) {
        continue;
      }
      try {
        candidates.push({
          payload: decodeSignedTransactionPayload(signedTransactionInfo),
          statusCode: Number(tx?.status),
        });
      } catch {
        // Ignore invalid entries and continue scanning.
      }
    }
  }

  if (!candidates.length) {
    return { payload: fallbackPayload };
  }

  candidates.sort((a, b) => {
    const aExpires = Number(a.payload?.expiresDate || 0);
    const bExpires = Number(b.payload?.expiresDate || 0);
    if (aExpires !== bExpires) {
      return bExpires - aExpires;
    }

    const aPurchase = Number(a.payload?.purchaseDate || 0);
    const bPurchase = Number(b.payload?.purchaseDate || 0);
    if (aPurchase !== bPurchase) {
      return bPurchase - aPurchase;
    }

    const aSigned = Number(a.payload?.signedDate || 0);
    const bSigned = Number(b.payload?.signedDate || 0);
    return bSigned - aSigned;
  });

  return candidates[0];
}

export async function verifyApplePurchase(input: VerifyPurchaseInput): Promise<NormalizedStorePurchase> {
  if (!isKnownBillingProduct(input.productId)) {
    throw new Error('Unknown Apple product id');
  }

  const payload = (input.payload || {}) as Record<string, any>;
  const extracted = extractSignedTransactionFromInput(input, payload);
  let signedTransactionInfo = String(extracted?.signedTransactionInfo || '').trim();
  let initialTransactionPayload = extracted?.transactionPayload || null;
  let transactionLookupResponse: Record<string, any> | null = null;
  const verificationSource: 'input_jws' | 'transaction_lookup' = extracted ? 'input_jws' : 'transaction_lookup';

  if (!initialTransactionPayload) {
    const fallbackTransactionId = firstNonEmptyString([
      input.transactionId,
      payload.transactionId,
      payload.id,
      payload.transactionIdentifierIOS,
      payload.originalTransactionIdentifierIOS,
      input.originalTransactionId,
    ]);
    if (!fallbackTransactionId) {
      throw new Error('Missing Apple transaction id');
    }

    transactionLookupResponse = (await fetchAppleTransactionInfo(fallbackTransactionId)) as Record<string, any>;
    signedTransactionInfo = String(transactionLookupResponse?.signedTransactionInfo || '').trim();
    if (!signedTransactionInfo) {
      throw new Error('Apple response missing signedTransactionInfo');
    }
    initialTransactionPayload = decodeSignedTransactionPayload(signedTransactionInfo);
  }

  if (!initialTransactionPayload || typeof initialTransactionPayload !== 'object') {
    throw new Error('Apple transaction payload missing or invalid');
  }

  const transactionId = firstNonEmptyString([
    initialTransactionPayload?.transactionId,
    input.transactionId,
    payload.transactionId,
    payload.id,
    payload.transactionIdentifierIOS,
  ]);
  if (!transactionId) {
    throw new Error('Apple transaction id is missing after decode');
  }

  const initialResolvedProductId = String(initialTransactionPayload?.productId || input.productId || '').trim();
  if (!initialResolvedProductId) {
    throw new Error('Apple transaction payload missing product id');
  }

  const expectedBundleId = String(process.env.APPLE_BUNDLE_ID || '').trim();
  const actualBundleId = String(initialTransactionPayload?.bundleId || '').trim();
  if (expectedBundleId && actualBundleId && expectedBundleId !== actualBundleId) {
    throw new Error('Apple bundle id mismatch during verification');
  }

  const originalTransactionId = String(
    initialTransactionPayload?.originalTransactionId ||
      payload?.originalTransactionId ||
      payload?.originalTransactionIdentifierIOS ||
      input.originalTransactionId ||
      transactionId,
  ).trim();

  const isCreditPack = isCreditProduct(initialResolvedProductId);
  const isSubscription = isSubscriptionProduct(initialResolvedProductId);

  let transactionPayload = initialTransactionPayload;
  let statusResponsePayload: Record<string, any> | null = null;
  let latestStatusCode: number | undefined;
  const resolvedEnvironment = String(
    transactionLookupResponse?.environment || payload?.environment || payload?.environmentIOS || 'unknown',
  );
  if (isSubscription) {
    try {
      const statusResponse = (await fetchAppleSubscriptionStatus(
        originalTransactionId || transactionId,
        resolvedEnvironment,
      )) as Record<string, any>;
      statusResponsePayload = statusResponse;
      const latest = pickLatestSubscriptionTransactionPayload(statusResponse, initialTransactionPayload);
      transactionPayload = latest.payload;
      latestStatusCode = latest.statusCode;
    } catch {
      // Fall back to transaction endpoint payload when status lookup fails.
      transactionPayload = initialTransactionPayload;
    }
  }

  const resolvedProductId = String(transactionPayload?.productId || input.productId || '').trim();
  if (!resolvedProductId) {
    throw new Error('Apple resolved product id is empty');
  }

  if (resolvedProductId !== input.productId) {
    const bothAreSubscriptions = isSubscriptionProduct(resolvedProductId) && isSubscriptionProduct(input.productId);
    if (!bothAreSubscriptions) {
      throw new Error('Apple product mismatch during verification');
    }
  }

  const expiresAt = isSubscription ? parseAppleDateMs(transactionPayload?.expiresDate) : undefined;

  const nowMs = Date.now();
  let status: 'active' | 'expired' | 'cancelled' | 'revoked' = 'active';
  if (isSubscription) {
    const expiryActive = !!(expiresAt && expiresAt.getTime() > nowMs);
    const statusActive =
      latestStatusCode === APPLE_SUBSCRIPTION_STATUS_ACTIVE ||
      latestStatusCode === APPLE_SUBSCRIPTION_STATUS_BILLING_RETRY ||
      latestStatusCode === APPLE_SUBSCRIPTION_STATUS_BILLING_GRACE;
    status = expiryActive || statusActive ? 'active' : 'expired';
  }

  if (!isSubscription && !isCreditPack) {
    throw new Error('Unsupported Apple product type for verification');
  }

  return {
    platform: 'ios',
    user_id: input.user_id,
    productId: resolvedProductId,
    transactionId,
    originalTransactionId,
    expiresAt,
    status,
    rawPayload: {
      verifier: 'apple_server_api',
      verificationSource,
      signedTransactionInfo,
      transactionPayload,
      environment: resolvedEnvironment,
      subscriptionStatusResponse: statusResponsePayload,
      subscriptionStatusCode: latestStatusCode,
      inputProductId: input.productId,
    },
  };
}
