import { type ApiTestCustodyWallet, apiTestSupport } from "@sdp/api/test-support";
import {
  confirmTransaction,
  createRpc,
  getMinimumBalanceForRentExemption,
  getRecentBlockhash,
} from "@sdp/rpc/solana";
import {
  type Address,
  appendTransactionMessageInstructions,
  compileTransaction,
  createNoopSigner,
  createTransactionMessage,
  getTransactionEncoder,
  pipe,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import { env } from "#env-impl";
import { getIntegrationCustodyProvider } from "./custody-provider";

const {
  app,
  clearTestDatabase,
  createKVStoreSet,
  createFeePaymentAdapter,
  createMosaicService,
  createSigningService,
  createToken2022Service,
  CustodyConfigStore,
  getDb,
  hashString,
  seedTestDatabase,
  TEST_ORG,
  TEST_PROJECT,
  TEST_PROJECT_API_KEY,
  TEST_PROJECT_CACHED_KEY,
  TEST_USER,
} = apiTestSupport;

const PRIVY_CONFIGURED = !!env.PRIVY_APP_ID && !!env.PRIVY_APP_SECRET;
const KORA_CONFIGURED = !!env.KORA_RPC_URL;
const RUN_INTEGRATION_TESTS = env.RUN_INTEGRATION_TESTS === "true";
const INTEGRATION_CUSTODY_PROVIDER = getIntegrationCustodyProvider();
const LOCAL_CUSTODY_CONFIGURED = !!env.CUSTODY_PRIVATE_KEY;
const INTEGRATION_CUSTODY_CONFIGURED =
  INTEGRATION_CUSTODY_PROVIDER === "local" ? LOCAL_CUSTODY_CONFIGURED : PRIVY_CONFIGURED;
const SOLANA_CONFIGURED = !!env.SOLANA_RPC_URL && INTEGRATION_CUSTODY_CONFIGURED;

let cachedKeyHash: string | null = null;
let cachedCustodyAddress: string | null = null;
// 0.05 SOL — enough headroom for sRFC-37 deploy paths where custody pays
// directly. A single tokenized-security deploy needs ~0.0095 SOL (mint +
// mintConfig + listConfig + extraMetas PDA rent, plus keeping custody itself
// rent-exempt). Earlier 0.01-SOL budget was a razor-thin fit for the
// stablecoin shape; the ScaledUiAmount extension on tokenized-security
// pushed the mint rent ~0.0004 SOL higher and tipped tests over.
const INTEGRATION_CUSTODY_FUND_LAMPORTS = 50_000_000;
const KORA_MAX_TRANSFER_LAMPORTS = 10_000_000n;

type SolanaRpcResponse<T> =
  | { jsonrpc: "2.0"; id: number; result: T }
  | { jsonrpc: "2.0"; id: number; error: { code: number; message: string; data?: unknown } };

function getOrganizationSettingsForIntegration(): string | null {
  if (INTEGRATION_CUSTODY_PROVIDER !== "local") {
    return null;
  }

  return JSON.stringify({
    providerOverrides: {
      custody: {
        local: true,
      },
    },
  });
}

async function computeApiKeyHash(): Promise<string> {
  if (cachedKeyHash) {
    return cachedKeyHash;
  }

  const pepper = (env as { API_KEY_PEPPER: string }).API_KEY_PEPPER;
  const hash = await hashString(TEST_PROJECT_API_KEY.raw, pepper);
  cachedKeyHash = hash;
  return hash;
}

export async function initIntegrationSuite() {
  await seedTestDatabase(env);

  const apiKeyHash = await computeApiKeyHash();
  const state = await resetIntegrationState(apiKeyHash);

  return { apiKeyHash, custodyAddress: state.custodyAddress };
}

export async function resetIntegrationState(
  apiKeyHash: string
): Promise<{ custodyAddress: string }> {
  const db = getDb(env);
  const { apiKeys: apiKeysKV, rateLimits: rateLimitKV } = createKVStoreSet(env);

  const rateLimitKeys = await rateLimitKV.list();
  for (const key of rateLimitKeys.keys) {
    await rateLimitKV.delete(key.name);
  }

  await db
    .prepare("DELETE FROM signing_requests")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM frozen_accounts")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM token_allowlist_statuses")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM token_allowlists")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM issuance_transaction_statuses")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM issuance_transactions")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM issued_token_extensions")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM issued_tokens")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM project_members")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM api_keys WHERE project_id IS NOT NULL")
    .run()
    .catch(() => {});
  await db
    .prepare("DELETE FROM projects")
    .run()
    .catch(() => {});

  await db
    .prepare(
      "INSERT OR REPLACE INTO organizations (id, name, slug, tier, status, settings) VALUES (?, ?, ?, 'individual', 'active', ?)"
    )
    .bind(TEST_ORG.id, TEST_ORG.name, TEST_ORG.slug, getOrganizationSettingsForIntegration())
    .run();

  await db
    .prepare(
      "INSERT OR REPLACE INTO users (id, email, email_verified, status) VALUES (?, ?, 1, 'active')"
    )
    .bind(TEST_USER.id, TEST_USER.email)
    .run();

  await db
    .prepare(
      `INSERT OR REPLACE INTO projects (id, organization_id, name, slug, environment, status, created_by)
       VALUES (?, ?, ?, ?, ?, ?, ?)`
    )
    .bind(
      TEST_PROJECT.id,
      TEST_PROJECT.organizationId,
      TEST_PROJECT.name,
      TEST_PROJECT.slug,
      TEST_PROJECT.environment,
      TEST_PROJECT.status,
      TEST_PROJECT.createdBy
    )
    .run();

  await db
    .prepare(
      `INSERT OR REPLACE INTO api_keys
       (id, organization_id, project_id, created_by, name, key_prefix, key_hash, role, permissions, status)
       VALUES (?, ?, ?, ?, 'Project Test Key', ?, ?, 'api_admin', '["*"]', 'active')`
    )
    .bind(
      TEST_PROJECT_API_KEY.id,
      TEST_ORG.id,
      TEST_PROJECT.id,
      TEST_USER.id,
      TEST_PROJECT_API_KEY.prefix,
      apiKeyHash
    )
    .run();

  await apiKeysKV.put(`key:${apiKeyHash}`, JSON.stringify(TEST_PROJECT_CACHED_KEY));
  cachedCustodyAddress = await ensureIntegrationCustodyAddress();
  return { custodyAddress: cachedCustodyAddress };
}

export async function cleanupIntegrationSuite() {
  await clearTestDatabase(env);
}

type IntegrationRequestInit = RequestInit & {
  timeoutMs?: number;
};

export async function request(url: string, init: IntegrationRequestInit = {}) {
  const { timeoutMs, ...requestInit } = init;

  if (!timeoutMs || timeoutMs <= 0) {
    return app.request(url, requestInit, env);
  }

  const controller = new AbortController();
  const operation = Promise.resolve(
    app.request(url, { ...requestInit, signal: controller.signal }, env)
  );
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
  void operation.catch(() => undefined);

  try {
    return await Promise.race([
      operation,
      new Promise<Response>((_resolve, reject) => {
        timeoutId = setTimeout(() => {
          controller.abort();
          reject(
            new Error(
              `Timed out waiting for ${requestInit.method ?? "GET"} ${url} after ${timeoutMs}ms`
            )
          );
        }, timeoutMs);
      }),
    ]);
  } finally {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
  }
}

export function requestWithApiKey(apiKey: string = TEST_PROJECT_API_KEY.raw) {
  return (url: string, init: IntegrationRequestInit = {}) => {
    const headers = new Headers(init.headers);
    headers.set("Authorization", `Bearer ${apiKey}`);
    return request(url, { ...init, headers });
  };
}

async function ensurePrivyCustodyAddress(): Promise<string> {
  if (!SOLANA_CONFIGURED) {
    return "";
  }

  const db = getDb(env);
  const signingService = createSigningService(env);
  const existing = await signingService.getConfigurationByProvider(TEST_ORG.id, undefined, "privy");

  if (!existing) {
    await signingService.initializePrivySigning(TEST_ORG.id, undefined, {
      walletLabel: "Integration Root Wallet",
    });
  } else {
    await signingService.setDefaultProvider(TEST_ORG.id, undefined, "privy");
  }

  const config = await signingService.getConfigurationByProvider(TEST_ORG.id, undefined, "privy");
  if (!config) {
    throw new Error("Integration precondition failed: Privy signer configuration not found.");
  }

  if (!config.defaultWalletId) {
    const fallbackWallet = await db
      .prepare(
        `SELECT wallet_id
       FROM custody_wallets
       WHERE custody_config_id = ? AND status = 'active'
       ORDER BY created_at ASC
       LIMIT 1`
      )
      .bind(config.id)
      .first<{ wallet_id: string }>();

    if (!fallbackWallet) {
      throw new Error("Integration precondition failed: Privy signer has no active wallets.");
    }

    await db
      .prepare(
        `UPDATE custody_configs
       SET default_wallet_id = ?, updated_at = datetime('now')
       WHERE id = ?`
      )
      .bind(fallbackWallet.wallet_id, config.id)
      .run();
  }

  const walletRows = (
    await db
      .prepare(
        `SELECT wallet_id, public_key
       FROM custody_wallets
       WHERE custody_config_id = ? AND status = 'active'
       ORDER BY created_at ASC`
      )
      .bind(config.id)
      .all<{ wallet_id: string; public_key: string }>()
  ).results;

  let preferredWallet = walletRows.find((wallet) => wallet.wallet_id === config.defaultWalletId);
  if (!preferredWallet) {
    preferredWallet = walletRows[0];
  }

  if (!preferredWallet) {
    throw new Error("Integration precondition failed: Privy signer has no active wallets.");
  }

  for (const wallet of walletRows) {
    // eslint-disable-next-line no-await-in-loop
    const exists = await solanaAccountExists(env.SOLANA_RPC_URL as string, wallet.public_key);
    if (!exists) {
      continue;
    }

    preferredWallet = wallet;
    break;
  }

  if (preferredWallet.wallet_id !== config.defaultWalletId) {
    await db
      .prepare(
        `UPDATE custody_configs
       SET default_wallet_id = ?, updated_at = datetime('now')
       WHERE id = ?`
      )
      .bind(preferredWallet.wallet_id, config.id)
      .run();
  }

  const address = preferredWallet.public_key;
  await ensureAddressAccountExists(address);
  // Top up existing wallets too — sRFC-37 deploy paths now have custody pay
  // directly, so a 1M-lamport bootstrap left over from older runs isn't enough.
  await fundAddressToLamports(address, INTEGRATION_CUSTODY_FUND_LAMPORTS);
  return address;
}

async function ensureIntegrationCustodyAddress(): Promise<string> {
  if (INTEGRATION_CUSTODY_PROVIDER === "local") {
    return ensureLocalCustodyAddress();
  }

  return ensurePrivyCustodyAddress();
}

async function ensureLocalCustodyAddress(): Promise<string> {
  if (!SOLANA_CONFIGURED) {
    return "";
  }

  const db = getDb(env);
  const signingService = createSigningService(env);
  const existing = await signingService.getConfigurationByProvider(TEST_ORG.id, undefined, "local");

  if (!existing) {
    const initialized = await signingService.initializeLocalSigning(TEST_ORG.id, undefined, {
      walletLabel: "Integration Local Root Wallet",
    });
    await ensureAddressAccountExists(initialized.publicKey);
    await fundAddressToLamports(initialized.publicKey, INTEGRATION_CUSTODY_FUND_LAMPORTS);
    return initialized.publicKey;
  }

  await signingService.setDefaultProvider(TEST_ORG.id, undefined, "local");
  const config = await signingService.getConfigurationByProvider(TEST_ORG.id, undefined, "local");
  if (!config) {
    throw new Error("Integration precondition failed: local signer configuration not found.");
  }

  const wallet = await db
    .prepare(
      `SELECT wallet_id, public_key
       FROM custody_wallets
       WHERE custody_config_id = ? AND status = 'active'
       ORDER BY CASE WHEN wallet_id = ? THEN 0 ELSE 1 END, created_at ASC
       LIMIT 1`
    )
    .bind(config.id, config.defaultWalletId ?? "")
    .first<{ wallet_id: string; public_key: string }>();

  if (!wallet) {
    throw new Error("Integration precondition failed: local signer has no active wallets.");
  }

  if (wallet.wallet_id !== config.defaultWalletId) {
    await db
      .prepare(
        `UPDATE custody_configs
         SET default_wallet_id = ?, updated_at = datetime('now')
         WHERE id = ?`
      )
      .bind(wallet.wallet_id, config.id)
      .run();
  }

  await ensureAddressAccountExists(wallet.public_key);
  await fundAddressToLamports(wallet.public_key, INTEGRATION_CUSTODY_FUND_LAMPORTS);
  return wallet.public_key;
}

async function ensureAddressAccountExists(address: string): Promise<void> {
  const rpcUrl = env.SOLANA_RPC_URL;
  if (!rpcUrl) {
    return;
  }

  const exists = await solanaAccountExists(rpcUrl, address);
  if (exists) {
    return;
  }

  let koraFundingError: unknown = null;
  const koraFunded = await fundAddressViaKoraFeePayer(address).catch((error) => {
    koraFundingError = error;
    return false;
  });
  if (koraFunded) {
    await waitForAccountExistence(rpcUrl, address, 30_000);
    return;
  }

  try {
    await solanaRequestAirdrop(rpcUrl, address, INTEGRATION_CUSTODY_FUND_LAMPORTS);
    await waitForAccountExistence(rpcUrl, address, 30_000);
  } catch (airdropError) {
    const koraMessage =
      koraFundingError instanceof Error ? koraFundingError.message : String(koraFundingError);
    const airdropMessage =
      airdropError instanceof Error ? airdropError.message : String(airdropError);

    throw new Error(
      `Failed to activate Privy signer account ${address}. ` +
        `Kora funding failed: ${koraMessage}. ` +
        `Airdrop failed: ${airdropMessage}`
    );
  }
}

async function getAddressLamports(address: string): Promise<number> {
  const rpcUrl = env.SOLANA_RPC_URL;
  if (!rpcUrl) {
    return 0;
  }

  type Balance = { value: number };
  const response = await solanaRpc<Balance>(rpcUrl, "getBalance", [
    address,
    { commitment: "confirmed" },
  ]);

  return response.value ?? 0;
}

async function fundAddressViaKoraFeePayer(
  address: string,
  lamports: bigint = BigInt(INTEGRATION_CUSTODY_FUND_LAMPORTS)
): Promise<boolean> {
  const rpcUrl = env.SOLANA_RPC_URL;
  if (!rpcUrl || !env.KORA_RPC_URL) {
    return false;
  }

  const feePayment = createFeePaymentAdapter(env);
  const feePayer = await feePayment.getFeePayer();
  const rpc = createRpc(env);
  let remainingLamports = lamports;

  while (remainingLamports > 0n) {
    const requestedAmount =
      remainingLamports > KORA_MAX_TRANSFER_LAMPORTS
        ? KORA_MAX_TRANSFER_LAMPORTS
        : remainingLamports;
    const { blockhash, lastValidBlockHeight } = await getRecentBlockhash(rpc, "confirmed");
    const minimumLamports = await getMinimumBalanceForRentExemption(rpc, 0);
    const amount = requestedAmount > minimumLamports ? requestedAmount : minimumLamports + 1n;

    const instruction = getTransferSolInstruction({
      source: createNoopSigner(feePayer),
      destination: address as Address,
      amount,
    });

    const message = pipe(
      createTransactionMessage({ version: 0 }),
      (m) => setTransactionMessageFeePayer(feePayer, m),
      (m) => setTransactionMessageLifetimeUsingBlockhash({ blockhash, lastValidBlockHeight }, m),
      (m) => appendTransactionMessageInstructions([instruction], m)
    );

    const compiled = compileTransaction(message);
    const txBytes = new Uint8Array(getTransactionEncoder().encode(compiled));
    const signature = await feePayment.signAndSend(txBytes);
    const confirmation = await confirmTransaction(rpc, signature, { commitment: "confirmed" });

    if (confirmation.err) {
      return false;
    }

    remainingLamports -= requestedAmount;
  }

  return true;
}

export async function fundAddressToLamports(
  address: string,
  minimumLamports: number
): Promise<void> {
  const currentLamports = await getAddressLamports(address);
  if (currentLamports >= minimumLamports) {
    return;
  }

  const requiredLamports = minimumLamports - currentLamports;
  let koraFundingError: unknown = null;
  const koraFunded = await fundAddressViaKoraFeePayer(address, BigInt(requiredLamports)).catch(
    (error) => {
      koraFundingError = error;
      return false;
    }
  );

  if (koraFunded) {
    await waitForLamports(address, minimumLamports, 30_000);
    return;
  }

  try {
    await solanaRequestAirdrop(env.SOLANA_RPC_URL as string, address, requiredLamports);
    await waitForLamports(address, minimumLamports, 30_000);
  } catch (airdropError) {
    const koraMessage =
      koraFundingError instanceof Error ? koraFundingError.message : String(koraFundingError);
    const airdropMessage =
      airdropError instanceof Error ? airdropError.message : String(airdropError);

    throw new Error(
      `Failed to fund wallet ${address} to ${minimumLamports} lamports. ` +
        `Kora funding failed: ${koraMessage}. ` +
        `Airdrop failed: ${airdropMessage}`
    );
  }
}

async function solanaAccountExists(rpcUrl: string, address: string): Promise<boolean> {
  type AccountInfo = { value: null | object };
  const response = await solanaRpc<AccountInfo>(rpcUrl, "getAccountInfo", [
    address,
    { encoding: "base64", commitment: "confirmed" },
  ]);
  return response.value !== null;
}

async function solanaRequestAirdrop(
  rpcUrl: string,
  address: string,
  lamports: number
): Promise<void> {
  await solanaRpc<string>(rpcUrl, "requestAirdrop", [address, lamports]);
}

async function waitForAccountExistence(
  rpcUrl: string,
  address: string,
  timeoutMs: number
): Promise<void> {
  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    // eslint-disable-next-line no-await-in-loop
    const exists = await solanaAccountExists(rpcUrl, address);
    if (exists) {
      return;
    }
    // eslint-disable-next-line no-await-in-loop
    await sleep(1_000);
  }

  throw new Error(`Timed out waiting for Privy signer account ${address} to exist on-chain.`);
}

async function waitForLamports(address: string, minimumLamports: number, timeoutMs: number) {
  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    // eslint-disable-next-line no-await-in-loop
    const lamports = await getAddressLamports(address);
    if (lamports >= minimumLamports) {
      return;
    }
    // eslint-disable-next-line no-await-in-loop
    await sleep(1_000);
  }

  throw new Error(`Timed out waiting for ${address} to reach ${minimumLamports} lamports.`);
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function solanaRpc<T>(rpcUrl: string, method: string, params: unknown[]): Promise<T> {
  const maxRetries = 4;
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(rpcUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
      });

      const payload = (await response.json()) as SolanaRpcResponse<T>;
      if ("error" in payload) {
        throw new Error(payload.error.message ?? `Solana RPC error calling ${method}`);
      }

      return payload.result;
    } catch (error) {
      if (attempt < maxRetries && isRetryableSolanaRpcError(error)) {
        await sleep((attempt + 1) * 500);
        continue;
      }

      throw error;
    }
  }

  throw new Error(`Solana RPC error calling ${method}`);
}

function isRetryableSolanaRpcError(error: unknown): boolean {
  if (!(error instanceof Error)) return false;
  const message = error.message.toLowerCase();
  return (
    message.includes("internal error") ||
    message.includes("unable to complete request") ||
    message.includes("request timed out") ||
    message.includes("timed out") ||
    message.includes("service unavailable") ||
    message.includes("try again") ||
    message.includes("too many requests") ||
    message.includes("429") ||
    message.includes("500") ||
    message.includes("502") ||
    message.includes("503") ||
    message.includes("504")
  );
}

export async function createFundedPrivyWallet(input: {
  label: string;
  fundLamports?: number;
  setDefault?: boolean;
}): Promise<ApiTestCustodyWallet> {
  const signingService = createSigningService(env);
  const wallet = await signingService.createWallet(TEST_ORG.id, undefined, {
    provider: "privy",
    label: input.label,
    setDefault: input.setDefault,
  });

  if (input.fundLamports && input.fundLamports > 0) {
    await fundAddressToLamports(wallet.publicKey, input.fundLamports);
  } else {
    await ensureAddressAccountExists(wallet.publicKey);
  }

  return wallet;
}

export async function createFundedIntegrationWallet(input: {
  label: string;
  fundLamports?: number;
  setDefault?: boolean;
}): Promise<ApiTestCustodyWallet> {
  if (INTEGRATION_CUSTODY_PROVIDER === "local") {
    return createFundedLocalWallet(input);
  }

  return createFundedPrivyWallet(input);
}

async function createFundedLocalWallet(input: {
  label: string;
  fundLamports?: number;
  setDefault?: boolean;
}): Promise<ApiTestCustodyWallet> {
  const publicKey = await ensureLocalCustodyAddress();
  const signingService = createSigningService(env);
  const config = await signingService.getConfigurationByProvider(TEST_ORG.id, undefined, "local");
  if (!config) {
    throw new Error("Integration precondition failed: local signer configuration not found.");
  }

  const configStore = new CustodyConfigStore(getDb(env), env);
  // Local custody has one signing key; access tests still need distinct wallet IDs.
  const wallet = await configStore.createWallet(config.id, {
    walletId: `local_${crypto.randomUUID()}`,
    publicKey,
    label: input.label,
    purpose: "transfer",
  });

  if (input.setDefault) {
    await getDb(env)
      .prepare(
        "UPDATE custody_configs SET default_wallet_id = ?, updated_at = datetime('now') WHERE id = ?"
      )
      .bind(wallet.walletId, config.id)
      .run();
  }

  if (input.fundLamports && input.fundLamports > 0) {
    await fundAddressToLamports(wallet.publicKey, input.fundLamports);
  } else {
    await ensureAddressAccountExists(wallet.publicKey);
  }

  return wallet;
}

export {
  app,
  createMosaicService,
  createToken2022Service,
  ensurePrivyCustodyAddress,
  env,
  INTEGRATION_CUSTODY_PROVIDER,
  KORA_CONFIGURED,
  PRIVY_CONFIGURED,
  RUN_INTEGRATION_TESTS,
  SOLANA_CONFIGURED,
  TEST_ORG,
  TEST_PROJECT,
  TEST_PROJECT_API_KEY,
  TEST_PROJECT_CACHED_KEY,
  TEST_USER,
};
