import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import net from 'node:net';
import os from 'node:os';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { prepareTestRuntime, serverTestEnv, TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD } from './helpers.js';

function reservePort() {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => {
      const address = server.address();
      const port = typeof address === 'object' && address ? address.port : 0;
      server.close((error) => error ? reject(error) : resolve(port));
    });
  });
}

async function rawRequest(base, pathname, { method = 'GET', token = '', body, idempotencyKey = '' } = {}) {
  const headers = { Accept: 'application/json', 'X-Request-ID': crypto.randomUUID() };
  if (token) headers.Authorization = `Bearer ${token}`;
  if (body != null) headers['Content-Type'] = 'application/json';
  if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
  const response = await fetch(`${base}${pathname}`, {
    method,
    headers,
    body: body == null ? undefined : JSON.stringify(body),
    signal: AbortSignal.timeout(15000)
  });
  const contentType = response.headers.get('content-type') || '';
  const payload = contentType.includes('application/json')
    ? await response.json().catch(() => ({}))
    : await response.text();
  return { status: response.status, ok: response.ok, payload };
}

async function request(base, pathname, options = {}) {
  const result = await rawRequest(base, pathname, options);
  if (!result.ok) {
    const payload = typeof result.payload === 'object' ? result.payload : {};
    throw Object.assign(new Error(payload.message || `${options.method || 'GET'} ${pathname} failed with ${result.status}`), {
      status: result.status,
      code: payload.code,
      payload
    });
  }
  return result.payload;
}

async function waitForHealth(base, child, output) {
  for (let attempt = 0; attempt < 140; attempt += 1) {
    if (child.exitCode != null) return false;
    try {
      const response = await fetch(`${base}/api/health`, { signal: AbortSignal.timeout(300) });
      if (response.ok) return true;
    } catch {}
    await new Promise((resolve) => setTimeout(resolve, 75));
  }
  throw new Error(`OnlyCoin final v1 HTTP server did not become healthy.\n${output.join('')}`);
}

function amountBig(value) {
  return BigInt(String(value || '0'));
}

test('final v1 identity, wallet, deposit-based presale and launch distribution flow', async (t) => {
  const port = await reservePort();
  const base = `http://127.0.0.1:${port}`;
  const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'onlycoin-final-v1-http-'));
  const output = [];
  const adminPassword = TEST_ADMIN_PASSWORD;
  const rootDir = path.resolve(import.meta.dirname, '../..');
  prepareTestRuntime(rootDir, dataDir, { APP_ORIGIN: base, TEST_TOKEN_TREASURY_AMOUNT: '1000000' });
  const child = spawn(process.execPath, ['--no-warnings', 'server/src/main.js'], {
    cwd: rootDir,
    env: serverTestEnv({ dataDir, port, base }),
    stdio: ['ignore', 'pipe', 'pipe'],
    windowsHide: true
  });
  child.stdout.on('data', (chunk) => output.push(chunk.toString('utf8')));
  child.stderr.on('data', (chunk) => output.push(chunk.toString('utf8')));

  t.after(async () => {
    if (child.exitCode == null) child.kill('SIGTERM');
    await new Promise((resolve) => {
      if (child.exitCode != null) return resolve();
      const timer = setTimeout(() => {
        if (child.exitCode == null) child.kill('SIGKILL');
        resolve();
      }, 3000);
      child.once('exit', () => { clearTimeout(timer); resolve(); });
    });
    fs.rmSync(dataDir, { recursive: true, force: true });
  });

  await waitForHealth(base, child, output);

  const admin = await request(base, '/api/admin/login', { method: 'POST', body: { email: TEST_ADMIN_EMAIL, password: adminPassword } });
  const adminToken = admin.token;
  assert.ok(adminToken);

  await request(base, '/api/admin/platform-settings', {
    method: 'PUT', token: adminToken,
    body: {
      email_registration_enabled: true,
      email_verification_required: true,
      social_login_enabled: true,
      presale_enabled: true,
      wallet_testnet_enabled: true,
      wallet_mainnet_enabled: true,
      wallet_address_cooldown_hours: 0,
      wallet_email_confirmation_required: true,
      project_token_contract: '0x1111111111111111111111111111111111111111',
      distribution_enabled: true,
      distribution_mode: 'auto_contract',
      distribution_signer_mode: 'test-signer',
      distribution_confirmations_required: 1
    }
  });
  await request(base, '/api/admin/settings', {
    method: 'PUT', token: adminToken,
    body: { conversion_enabled: true, conversion_unlock_at_launch: false }
  });

  const register = await request(base, '/api/auth/register', {
    method: 'POST',
    body: {
      email: 'final-v1-user@example.com',
      username: 'FinalV1User',
      displayName: 'Final V1 User',
      password: 'OnlyCoinTest2026A',
      confirmPassword: 'OnlyCoinTest2026A'
    }
  });
  assert.equal(register.verificationRequired, true);
  assert.match(register.debugVerificationCode, /^\d{6}$/);

  const verified = await request(base, '/api/auth/email/verify', {
    method: 'POST',
    body: { email: 'final-v1-user@example.com', code: register.debugVerificationCode }
  });
  const userToken = verified.token;
  assert.ok(userToken);
  assert.ok(verified.user.emailVerifiedAt);

  const login = await request(base, '/api/auth/email/login', {
    method: 'POST',
    body: { email: 'final-v1-user@example.com', password: 'OnlyCoinTest2026A' }
  });
  assert.equal(login.user.email, 'final-v1-user@example.com');

  const resetRequest = await request(base, '/api/auth/password/request', {
    method: 'POST', body: { email: 'final-v1-user@example.com' }
  });
  assert.ok(resetRequest.debugResetToken);
  await request(base, '/api/auth/password/reset', {
    method: 'POST',
    body: { token: resetRequest.debugResetToken, password: 'OnlyCoinTest2026B', confirmPassword: 'OnlyCoinTest2026B' }
  });
  const relogin = await request(base, '/api/auth/email/login', {
    method: 'POST', body: { email: 'final-v1-user@example.com', password: 'OnlyCoinTest2026B' }
  });
  assert.ok(relogin.token);

  const testnet = await request(base, '/api/account/wallets/testnet', {
    method: 'POST', token: relogin.token,
    body: {
      password: 'OnlyCoinWallet2026A',
      confirmPassword: 'OnlyCoinWallet2026A',
      acknowledgeBackup: true,
      label: 'Testnet Cüzdanım'
    }
  });
  assert.match(testnet.wallet.address, /^0x[0-9a-f]{40}$/);
  assert.ok(testnet.recoveryToken);
  const passwordCheck = await request(base, `/api/account/wallets/${testnet.wallet.id}/verify-password`, {
    method: 'POST', token: relogin.token, body: { password: 'OnlyCoinWallet2026A' }
  });
  assert.equal(passwordCheck.ok, true);

  const recovery = await request(base, `/api/account/wallets/recovery/${encodeURIComponent(testnet.recoveryToken)}`, { token: relogin.token });
  assert.equal(recovery.format, 'onlycoin-encrypted-wallet-v1');
  assert.equal(recovery.address, testnet.wallet.address);
  const recoveryAgain = await rawRequest(base, `/api/account/wallets/recovery/${encodeURIComponent(testnet.recoveryToken)}`, { token: relogin.token });
  assert.equal(recoveryAgain.status, 409);
  assert.equal(recoveryAgain.payload.code, 'RECOVERY_EXPORT_USED');

  const mainnet = await request(base, '/api/account/wallets/mainnet', {
    method: 'POST', token: relogin.token,
    body: {
      address: '0x2222222222222222222222222222222222222222',
      confirmAddress: '0x2222222222222222222222222222222222222222',
      label: 'Lansman Cüzdanım',
      network: 'BSC Mainnet',
      chainId: 56
    }
  });
  assert.match(mainnet.debugConfirmationCode, /^\d{6}$/);
  const confirmedWallet = await request(base, `/api/account/wallets/${mainnet.wallet.id}/confirm`, {
    method: 'POST', token: relogin.token, body: { code: mainnet.debugConfirmationCode }
  });
  assert.equal(confirmedWallet.verificationStatus, 'email_confirmed');
  assert.equal(confirmedWallet.status, 'active');

  await request(base, `/api/admin/users/${verified.user.id}/grant-points`, {
    method: 'POST', token: adminToken, idempotencyKey: 'final-v1-points-grant-0001',
    body: { amount: 2000, reason: 'Final akış dönüşüm testi için kontrollü tahsis' }
  });
  const conversion = await request(base, '/api/game/convert', {
    method: 'POST', token: relogin.token, idempotencyKey: 'final-v1-convert-0001', body: { points: 1000 }
  });
  assert.equal(conversion.tokensReceived, 100);
  const walletBeforePresale = await request(base, '/api/game/wallet', { token: relogin.token });
  assert.equal(walletBeforePresale.balances.OC_TOKEN, 100);

  const initialAllocation = await request(base, '/api/admin/presale-allocation', { token: adminToken });
  const cfg = initialAllocation.config;
  await request(base, '/api/admin/presale-allocation/settings', {
    method: 'PUT', token: adminToken,
    body: {
      policy: 'launch_price',
      totalSupplyTokenBase: cfg.totalSupplyTokenBase,
      poolTokenBase: cfg.poolTokenBase,
      referencePriceUsdtMicros: 12500,
      launchPriceUsdtMicros: 20000,
      termsVersion: '1.0',
      autoFinalizeAtLaunch: false
    }
  });

  const order = await request(base, '/api/game/presale/order', {
    method: 'POST', token: relogin.token, idempotencyKey: 'final-v1-presale-order-0001',
    body: { amountUsdtMicros: 10_000_000 }
  });
  assert.equal(order.estimate.depositUsdtMicros, 10_000_000);
  const duplicateOrder = await request(base, '/api/game/presale/order', {
    method: 'POST', token: relogin.token, idempotencyKey: 'final-v1-presale-order-0001',
    body: { amountUsdtMicros: 10_000_000 }
  });
  assert.equal(duplicateOrder.duplicate, true);

  await request(base, `/api/admin/payment-orders/${order.order.id}/approve`, {
    method: 'POST', token: adminToken, body: {}
  });
  const summaryAt20 = await request(base, '/api/game/presale/summary', { token: relogin.token });
  assert.equal(summaryAt20.totalDepositUsdtMicros, 10_000_000);
  assert.ok(amountBig(summaryAt20.estimatedTokenBase) > 0n);

  const cfgAt20 = (await request(base, '/api/admin/presale-allocation', { token: adminToken })).config;
  await request(base, '/api/admin/presale-allocation/settings', {
    method: 'PUT', token: adminToken,
    body: {
      policy: 'launch_price',
      totalSupplyTokenBase: cfgAt20.totalSupplyTokenBase,
      poolTokenBase: cfgAt20.poolTokenBase,
      referencePriceUsdtMicros: 12500,
      launchPriceUsdtMicros: 40000,
      termsVersion: '1.0',
      autoFinalizeAtLaunch: false
    }
  });
  const summaryAt40 = await request(base, '/api/game/presale/summary', { token: relogin.token });
  assert.equal(summaryAt40.totalDepositUsdtMicros, 10_000_000, 'deposit amount must remain immutable');
  assert.ok(amountBig(summaryAt40.estimatedTokenBase) < amountBig(summaryAt20.estimatedTokenBase), 'current estimate must follow admin price changes');

  const snapshot = await request(base, '/api/admin/presale-allocation/snapshots', { method: 'POST', token: adminToken, body: {} });
  assert.equal(snapshot.preview.purchaseCount, 1);
  const approved = await request(base, `/api/admin/presale-allocation/snapshots/${snapshot.snapshot.id}/approve`, {
    method: 'POST', token: adminToken, body: { note: 'Final v1 automated test approval' }
  });
  assert.equal(approved.ready, true);
  const finalized = await request(base, `/api/admin/presale-allocation/snapshots/${snapshot.snapshot.id}/finalize`, {
    method: 'POST', token: adminToken, body: {}
  });
  assert.equal(finalized.snapshot.status, 'finalized');
  const finalizedAgain = await request(base, `/api/admin/presale-allocation/snapshots/${snapshot.snapshot.id}/finalize`, {
    method: 'POST', token: adminToken, body: {}
  });
  assert.equal(finalizedAgain.duplicate, true);

  const finalSummary = await request(base, '/api/game/presale/summary', { token: relogin.token });
  assert.ok(amountBig(finalSummary.finalizedTokenBase) > 0n);
  assert.equal(finalSummary.locked, true);

  const campaign = await request(base, '/api/admin/distributions', {
    method: 'POST', token: adminToken,
    body: { name: 'Final V1 Testnet Lansman Dağıtımı', mode: 'auto_contract' }
  });
  assert.equal(campaign.campaign.mode, 'auto_contract');
  assert.equal(campaign.items.length, 1);
  assert.equal(campaign.items[0].status, 'ready');
  assert.equal(campaign.items[0].game_token_whole, 100);
  assert.equal(campaign.items[0].presale_token_base, finalSummary.finalizedTokenBase);
  assert.equal(amountBig(campaign.items[0].amount_token_base), amountBig(finalSummary.finalizedTokenBase) + 100n * (10n ** 18n));
  const walletAfterCampaign = await request(base, '/api/game/wallet', { token: relogin.token });
  assert.equal(walletAfterCampaign.balances.OC_TOKEN, 0, 'accumulated OC_TOKEN must be locked exactly once for launch distribution');
  assert.equal(walletAfterCampaign.distributions.length, 1);
  assert.equal(walletAfterCampaign.distributions[0].game_token_whole, 100);

  const run = await request(base, `/api/admin/distributions/${campaign.campaign.id}/run`, {
    method: 'POST', token: adminToken, body: {}
  });
  assert.equal(run.processed, 1);
  const broadcastItem = run.items[0];
  assert.equal(broadcastItem.status, 'broadcast');
  assert.match(broadcastItem.tx_hash, /^0x[0-9a-f]{64}$/);

  const confirmedDistribution = await request(base, `/api/admin/distribution-items/${broadcastItem.id}/confirm`, {
    method: 'POST', token: adminToken,
    body: {
      txHash: broadcastItem.tx_hash,
      chainId: 56,
      contract: '0x1111111111111111111111111111111111111111',
      recipient: '0x2222222222222222222222222222222222222222',
      amountBase: broadcastItem.amount_token_base,
      confirmations: 1,
      blockNumber: '12345'
    }
  });
  assert.equal(confirmedDistribution.item.status, 'confirmed');
  const confirmedDuplicate = await request(base, `/api/admin/distribution-items/${broadcastItem.id}/confirm`, {
    method: 'POST', token: adminToken,
    body: { txHash: broadcastItem.tx_hash }
  });
  assert.equal(confirmedDuplicate.duplicate, true);

  const provider = await request(base, '/api/admin/auth-providers/google', {
    method: 'PUT', token: adminToken,
    body: {
      name: 'Google', enabled: true, clientId: 'google-client-id-test',
      secrets: { clientSecret: 'google-client-secret-test' },
      scopes: 'openid email profile', sortOrder: 10
    }
  });
  assert.equal(provider.enabled, true);
  assert.match(provider.secrets.clientSecret, /••••••••/);

  const publicAuth = await request(base, '/api/public/auth');
  assert.ok(publicAuth.providers.some((item) => item.id === 'google'));

  const outbox = await request(base, '/api/admin/email-outbox', { token: adminToken });
  assert.ok(outbox.some((message) => message.template === 'email-verification'));
  assert.ok(outbox.some((message) => message.template === 'wallet-created'));

  for (const route of ['/', '/presale', '/tokenomics', '/roadmap', '/whitepaper', '/game/', '/admin/']) {
    const response = await fetch(`${base}${route}`, { signal: AbortSignal.timeout(5000) });
    assert.equal(response.status, 200, `${route} must return 200`);
    const html = await response.text();
    assert.match(html, /OnlyCoin|ONLYCOIN/i, `${route} should render OnlyCoin HTML`);
  }
});
