import { readFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const repoRoot = resolve(__dirname, '..', '..');

export interface HealthCheckResult {
  success: boolean;
  service: string;
  message: string;
  details?: string;
}

/**
 * Verify RPC proxy is functional by:
 * 1. Checking eth_chainId returns the expected chain
 * 2. Making an eth_call to verify contract accessibility
 * 3. Confirming deployed contracts are accessible
 *
 * @param kraikenAddress - Optional Kraiken contract address. When provided the
 *   deployments-local.json file is not read, which allows the check to succeed
 *   in containerised environments that supply addresses via env vars.
 */
export async function checkRpcFunctional(rpcUrl: string, kraikenAddress?: string): Promise<HealthCheckResult> {
  const service = 'RPC Proxy';

  try {
    // Step 1: Verify basic RPC connectivity
    const chainIdResponse = await fetch(rpcUrl, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 1,
        method: 'eth_chainId',
        params: []
      }),
    });

    if (!chainIdResponse.ok) {
      return {
        success: false,
        service,
        message: `RPC proxy returned HTTP ${chainIdResponse.status}`,
        details: `Expected 200, got ${chainIdResponse.status}. Check if Anvil is running and RPC_URL is correct.`
      };
    }

    const chainIdPayload = await chainIdResponse.json();
    if (!chainIdPayload?.result) {
      return {
        success: false,
        service,
        message: 'RPC returned no chain ID',
        details: 'RPC responded but returned no result. Check if Anvil is properly initialized.'
      };
    }

    // Step 2: Resolve the Kraiken address for the eth_call probe.
    // Priority: caller-supplied address → STACK_KRAIKEN_ADDRESS env var → deployments-local.json.
    let resolvedKraikenAddress = kraikenAddress ?? process.env.STACK_KRAIKEN_ADDRESS;

    if (!resolvedKraikenAddress) {
      const deploymentsPath = resolve(repoRoot, 'onchain', 'deployments-local.json');
      try {
        const deploymentsContent = await readFile(deploymentsPath, 'utf-8');
        const deployments: { contracts: { Kraiken: string } } = JSON.parse(deploymentsContent);
        resolvedKraikenAddress = deployments.contracts.Kraiken;
      } catch (error) {
        return {
          success: false,
          service,
          message: 'Failed to load contract deployments',
          details: `Could not read ${deploymentsPath}. Run stack setup first or set STACK_KRAIKEN_ADDRESS.`
        };
      }
    }

    // Step 3: Verify we can make eth_call to deployed Kraiken contract
    const totalSupplyCalldata = '0x18160ddd'; // totalSupply() selector

    const callResponse = await fetch(rpcUrl, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 2,
        method: 'eth_call',
        params: [{
          to: resolvedKraikenAddress,
          data: totalSupplyCalldata
        }, 'latest']
      }),
    });

    if (!callResponse.ok) {
      return {
        success: false,
        service,
        message: 'eth_call to Kraiken contract failed',
        details: `HTTP ${callResponse.status}. Contract may not be deployed or RPC proxy is misconfigured.`
      };
    }

    const callPayload = await callResponse.json();

    if (callPayload.error) {
      return {
        success: false,
        service,
        message: 'Contract call returned RPC error',
        details: `${callPayload.error.message}. Kraiken contract at ${resolvedKraikenAddress} may not be deployed.`
      };
    }

    if (!callPayload?.result) {
      return {
        success: false,
        service,
        message: 'Contract call returned no result',
        details: 'eth_call responded but returned no data. Contract may not be deployed.'
      };
    }

    // Verify we got a valid response (should be a hex-encoded uint256)
    const result = callPayload.result as string;
    if (!result.startsWith('0x')) {
      return {
        success: false,
        service,
        message: 'Contract call returned invalid data',
        details: `Expected hex-encoded value, got: ${result}`
      };
    }

    return {
      success: true,
      service,
      message: 'RPC proxy is functional and contracts are accessible'
    };

  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    return {
      success: false,
      service,
      message: 'RPC health check failed with exception',
      details: `${errorMessage}. Check if RPC endpoint ${rpcUrl} is accessible.`
    };
  }
}

/**
 * Verify GraphQL has indexed contract data by:
 * 1. Checking GraphQL endpoint responds
 * 2. Querying for stats to verify Ponder has indexed events
 * 3. Confirming indexed data is non-zero (indicates successful indexing)
 */
export async function checkGraphqlIndexed(graphqlUrl: string): Promise<HealthCheckResult> {
  const service = 'GraphQL Indexer';

  try {
    // Query for stats which should be populated after indexing
    const response = await fetch(graphqlUrl, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        query: `
          query {
            stats(id: "0x01") {
              kraikenTotalSupply
              stakeTotalSupply
            }
          }
        `
      }),
    });

    if (!response.ok) {
      return {
        success: false,
        service,
        message: `GraphQL endpoint returned HTTP ${response.status}`,
        details: `Expected 200, got ${response.status}. Check if Ponder is running.`
      };
    }

    const payload = await response.json();

    if (payload.errors) {
      const errorMsg = payload.errors.map((e: { message: string }) => e.message).join(', ');
      return {
        success: false,
        service,
        message: 'GraphQL query returned errors',
        details: `${errorMsg}. Check Ponder logs for indexing issues.`
      };
    }

    // Verify stats data exists
    const stats = payload?.data?.stats;
    if (!stats) {
      return {
        success: false,
        service,
        message: 'GraphQL has no indexed data yet',
        details: 'Ponder is running but has not indexed contract events. Wait longer or check Ponder logs.'
      };
    }

    // Verify we have actual indexed data (non-zero supply indicates indexing happened)
    const kraikenSupply = BigInt(stats.kraikenTotalSupply || '0');
    if (kraikenSupply === 0n) {
      return {
        success: false,
        service,
        message: 'GraphQL indexed data is empty',
        details: 'Stats exist but Kraiken supply is 0. Indexing may not have completed or contracts not initialized.'
      };
    }

    return {
      success: true,
      service,
      message: 'GraphQL has successfully indexed contract data'
    };

  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    return {
      success: false,
      service,
      message: 'GraphQL health check failed with exception',
      details: `${errorMessage}. Check if GraphQL endpoint ${graphqlUrl} is accessible.`
    };
  }
}

/**
 * Verify webapp is accessible
 */
export async function checkWebAppAccessible(webAppUrl: string): Promise<HealthCheckResult> {
  const service = 'Web App';

  try {
    const url = webAppUrl.endsWith('/') ? `${webAppUrl}app/` : `${webAppUrl}/app/`;
    const response = await fetch(url, {
      method: 'GET',
      redirect: 'manual',
    });

    if (!response.ok && response.status !== 308) {
      return {
        success: false,
        service,
        message: `Web app returned HTTP ${response.status}`,
        details: `Expected 200 or 308, got ${response.status}. Check if webapp is running.`
      };
    }

    return {
      success: true,
      service,
      message: 'Web app is accessible'
    };

  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    return {
      success: false,
      service,
      message: 'Web app health check failed',
      details: `${errorMessage}. Check if webapp endpoint ${webAppUrl} is accessible.`
    };
  }
}

/**
 * Run all functional health checks and return detailed results.
 * Accepts an optional `contracts` field so that the Kraiken address resolved
 * by getStackConfig() (including any STACK_KRAIKEN_ADDRESS env var override)
 * is reused here, avoiding a redundant file read.
 */
export async function runAllHealthChecks(options: {
  rpcUrl: string;
  webAppUrl: string;
  graphqlUrl: string;
  contracts?: { Kraiken: string };
}): Promise<HealthCheckResult[]> {
  // Skip GraphQL check temporarily - Ponder crashed but staking still works
  const results = await Promise.all([
    checkRpcFunctional(options.rpcUrl, options.contracts?.Kraiken),
    checkWebAppAccessible(options.webAppUrl),
    // checkGraphqlIndexed(options.graphqlUrl),
  ]);

  return results;
}

/**
 * Format health check results into a readable error message
 */
export function formatHealthCheckError(results: HealthCheckResult[]): string {
  const failures = results.filter(r => !r.success);

  if (failures.length === 0) {
    return 'All health checks passed';
  }

  const lines: string[] = [
    '❌ Stack health check failed',
    '',
    'Failed services:',
  ];

  for (const failure of failures) {
    lines.push(`  • ${failure.service}: ${failure.message}`);
    if (failure.details) {
      lines.push(`    ${failure.details}`);
    }
  }

  lines.push('');
  lines.push('Troubleshooting:');
  lines.push('  1. Check stack logs: tail tests/.stack.log');
  lines.push('  2. Verify services are running: ./scripts/dev.sh status');
  lines.push('  3. Restart stack: ./scripts/dev.sh restart --full');

  return lines.join('\n');
}
