const http = require('http');
const https = require('https');
const CallbackEvent = require('../models/CallbackEvent');
const persistentStore = require('./persistentStore');

const SERVICE_CALLBACK_ENV_KEYS = Object.freeze({
  AISFOR: 'AISFOR_CHAIN_CALLBACK_AISFOR',
  AISTALK: 'AISFOR_CHAIN_CALLBACK_AISTALK',
  AISGROUP: 'AISFOR_CHAIN_CALLBACK_AISGROUP',
  AISMALL: 'AISFOR_CHAIN_CALLBACK_AISMALL'
});

const state = persistentStore.readState();
const callbackEvents = state.callbackEvents;

function getServiceCallbackUrl(serviceName) {
  const key = SERVICE_CALLBACK_ENV_KEYS[String(serviceName || '').trim().toUpperCase()];
  return key ? String(process.env[key] || '').trim() : '';
}

function getCallbackSecret(serviceName) {
  const normalized = String(serviceName || '').trim().toUpperCase();
  return String(
    process.env[`AISFOR_CHAIN_CALLBACK_SECRET_${normalized}`] ||
    process.env.AISFOR_CHAIN_CALLBACK_SECRET ||
    ''
  ).trim();
}

function listCallbacks() {
  return callbackEvents;
}

function getCallback(callbackId) {
  return callbackEvents.find(event => event.id === callbackId);
}

function getCallbackStats() {
  const byStatus = {};
  const byService = {};

  for (const event of callbackEvents) {
    byStatus[event.status || 'UNKNOWN'] = (byStatus[event.status || 'UNKNOWN'] || 0) + 1;
    byService[event.sourceService || 'UNKNOWN'] = (byService[event.sourceService || 'UNKNOWN'] || 0) + 1;
  }

  return {
    totalCallbacks: callbackEvents.length,
    pendingCallbacks: callbackEvents.filter(event => event.status === 'PENDING' || event.status === 'FAILED').length,
    deliveredCallbacks: callbackEvents.filter(event => event.status === 'DELIVERED' || event.status === 'MOCK_DELIVERED').length,
    byStatus,
    byService,
    latestCreatedAt: callbackEvents[0]?.createdAt || null
  };
}

function getServiceConfirmationRows() {
  const latestByService = new Map();

  for (const event of callbackEvents) {
    const serviceName = event.sourceService || 'UNKNOWN';
    if (!latestByService.has(serviceName)) {
      latestByService.set(serviceName, event);
    }
  }

  const serviceNames = new Set([
    ...Object.keys(SERVICE_CALLBACK_ENV_KEYS),
    ...latestByService.keys()
  ]);

  return Array.from(serviceNames).sort().map((serviceName) => {
    const event = latestByService.get(serviceName);
    const payload = event?.payload || {};
    const delivered = event?.status === 'DELIVERED' || event?.status === 'MOCK_DELIVERED';

    return {
      serviceName,
      chainConfirmed: Boolean(payload.chainConfirmed) && delivered,
      callbackStatus: event?.status || 'NO_CALLBACK',
      blockHeight: payload.blockHeight || event?.blockHeight || null,
      txHash: payload.txHash || null,
      proofId: payload.proofId || event?.proofId || null,
      targetId: payload.targetId || null,
      sourceType: payload.proofType || event?.callbackType || null,
      confirmedAt: event?.deliveredAt || event?.updatedAt || null,
      lastError: event?.lastError || null
    };
  });
}

function findDuplicate({ blockId, proofId, sourceService, callbackType }) {
  return callbackEvents.find(event => (
    event.blockId === blockId &&
    event.proofId === proofId &&
    event.sourceService === sourceService &&
    event.callbackType === callbackType
  ));
}

function createFinalizedCallback({ block, proof, transaction }) {
  const sourceService = proof?.sourceService || transaction?.sourceService || 'UNKNOWN';
  const callbackType = 'BLOCK_FINALIZED';
  const duplicate = findDuplicate({
    blockId: block.id,
    proofId: proof?.id || null,
    sourceService,
    callbackType
  });

  if (duplicate) {
    return {
      event: duplicate,
      created: false
    };
  }

  const payload = {
    eventType: callbackType,
    sourceService,
    chainConfirmed: true,
    chainStatus: block.status,
    blockId: block.id,
    blockHeight: block.height,
    blockHash: block.blockHash,
    blockFinalizedAt: block.finalizedAt || block.validation?.finalizedAt || null,
    proofId: proof?.id || null,
    proofType: proof?.proofType || null,
    externalProofId: proof?.externalProofId || null,
    idempotencyKey: proof?.idempotencyKey || null,
    actorId: proof?.actorId || transaction?.actorId || null,
    targetId: proof?.targetId || transaction?.targetId || null,
    transactionId: transaction?.id || proof?.chainTransactionId || null,
    txHash: transaction?.txHash || proof?.txHash || null
  };

  const event = new CallbackEvent({
    sourceService,
    callbackType,
    blockId: block.id,
    blockHeight: block.height,
    blockHash: block.blockHash,
    proofId: proof?.id || null,
    transactionId: transaction?.id || proof?.chainTransactionId || null,
    targetUrl: getServiceCallbackUrl(sourceService),
    payload
  });

  callbackEvents.unshift(event);
  persistentStore.writeState(state);

  return {
    event,
    created: true
  };
}

function postJson(url, payload, { callbackSecret, timeoutMs = 3000 } = {}) {
  return new Promise((resolve, reject) => {
    let parsedUrl;
    try {
      parsedUrl = new URL(url);
    } catch (err) {
      err.code = 'INVALID_CALLBACK_URL';
      reject(err);
      return;
    }

    const body = JSON.stringify(payload);
    const client = parsedUrl.protocol === 'https:' ? https : http;
    const request = client.request({
      method: 'POST',
      hostname: parsedUrl.hostname,
      port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
      path: `${parsedUrl.pathname}${parsedUrl.search}`,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
        'X-AISFOR-CHAIN-EVENT': payload.eventType || 'CHAIN_EVENT',
        'X-AISFOR-CHAIN-BLOCK': String(payload.blockHeight || ''),
        ...(callbackSecret ? { 'X-AISFOR-CHAIN-CALLBACK-SECRET': callbackSecret } : {})
      },
      timeout: timeoutMs
    }, (response) => {
      response.resume();
      response.on('end', () => {
        resolve({
          statusCode: response.statusCode || 0
        });
      });
    });

    request.on('timeout', () => {
      request.destroy(new Error('CALLBACK_TIMEOUT'));
    });
    request.on('error', reject);
    request.write(body);
    request.end();
  });
}

async function deliverCallback(callbackId) {
  const event = getCallback(callbackId);
  if (!event) return null;

  if (!event.targetUrl) {
    event.status = 'MOCK_DELIVERED';
    event.deliveredAt = event.deliveredAt || new Date().toISOString();
    event.updatedAt = new Date().toISOString();
    persistentStore.writeState(state);
    return event;
  }

  event.status = 'SENDING';
  event.attempts = Number(event.attempts || 0) + 1;
  event.updatedAt = new Date().toISOString();
  persistentStore.writeState(state);

  try {
    const result = await postJson(event.targetUrl, event.payload, {
      callbackSecret: getCallbackSecret(event.sourceService)
    });
    event.lastStatusCode = result.statusCode;
    event.status = result.statusCode >= 200 && result.statusCode < 300 ? 'DELIVERED' : 'FAILED';
    event.lastError = event.status === 'FAILED' ? `HTTP_${result.statusCode}` : null;
    event.deliveredAt = event.status === 'DELIVERED' ? new Date().toISOString() : event.deliveredAt;
  } catch (err) {
    event.status = 'FAILED';
    event.lastError = err.code || err.message || 'CALLBACK_ERROR';
  }

  event.updatedAt = new Date().toISOString();
  persistentStore.writeState(state);
  return event;
}

async function createAndDeliverFinalizedCallbacks(block) {
  const created = [];
  const delivered = [];
  const proofIds = new Set(block.proofIds || []);

  for (const proof of state.proofs.filter(row => proofIds.has(row.id))) {
    const transaction = state.transactions.find(row => row.id === proof.chainTransactionId);
    const result = createFinalizedCallback({ block, proof, transaction });
    created.push(result.event);
    delivered.push(await deliverCallback(result.event.id));
  }

  return {
    created,
    delivered
  };
}

async function retryCallback(callbackId) {
  return deliverCallback(callbackId);
}

module.exports = {
  SERVICE_CALLBACK_ENV_KEYS,
  getCallbackSecret,
  getServiceCallbackUrl,
  listCallbacks,
  getCallback,
  getCallbackStats,
  getServiceConfirmationRows,
  createFinalizedCallback,
  createAndDeliverFinalizedCallbacks,
  deliverCallback,
  retryCallback
};

