const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { createRequire } = require('module');

const CHAIN_URL = process.env.AISFOR_CHAIN_URL || 'http://localhost:4317';

function readEnvFile(root) {
  const envPath = path.join(root, '.env');
  const values = {};
  if (!fs.existsSync(envPath)) return values;

  const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/);
  for (const line of lines) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;
    const eq = trimmed.indexOf('=');
    if (eq < 0) continue;
    const key = trimmed.slice(0, eq).trim();
    let value = trimmed.slice(eq + 1).trim();
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1);
    }
    values[key] = value;
  }
  return values;
}

function serviceRequire(root) {
  return createRequire(path.join(root, 'package.json'));
}

function sha(value) {
  return crypto.createHash('sha256').update(String(value)).digest('hex');
}

async function postJson(url, body) {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
  const text = await response.text();
  let data = {};
  try {
    data = text ? JSON.parse(text) : {};
  } catch (err) {
    data = { raw: text };
  }
  if (!response.ok || data.ok === false) {
    const error = new Error(data.message || data.error || `HTTP_${response.status}`);
    error.status = response.status;
    error.data = data;
    throw error;
  }
  return data;
}

async function getJson(url) {
  const response = await fetch(url);
  const data = await response.json();
  if (!response.ok || data.ok === false) {
    const error = new Error(data.message || data.error || `HTTP_${response.status}`);
    error.status = response.status;
    error.data = data;
    throw error;
  }
  return data;
}

async function connectService(root, fallbackMongoUri) {
  const req = serviceRequire(root);
  const mongoose = req('mongoose');
  const env = readEnvFile(root);
  const mongoUri = env.MONGO_URI || fallbackMongoUri;
  await mongoose.connect(mongoUri);
  return { req, mongoose };
}

async function createAisforBundle(runId) {
  const root = 'E:\\aisfor';
  const { req, mongoose } = await connectService(root, 'mongodb://127.0.0.1:27017/aisfor');
  const BlockchainProofBundle = req('./models/BlockchainProofBundle');
  const bundleKey = `chain-e2e-${runId}-aisfor`;
  const contentHash = sha(`${bundleKey}:content`);
  const payloadHash = sha(`${bundleKey}:payload`);
  const bundleHash = sha(`${bundleKey}:bundle`);

  await BlockchainProofBundle.deleteOne({ bundleKey });
  const bundle = await BlockchainProofBundle.create({
    bundleKey,
    bundleType: 'storage_point_snapshot',
    storageProofFromSeq: 0,
    storageProofToSeq: 0,
    storageProofCount: 0,
    storageProofRootHash: '',
    pointProofFromSeq: 0,
    pointProofToSeq: 0,
    pointProofCount: 0,
    pointProofRootHash: '',
    combinedRootHash: contentHash,
    payloadHash,
    previousBundleHash: '',
    bundleHash,
    payload: {
      e2e: true,
      runId
    },
    status: 'prepared'
  });

  return {
    service: 'AISFOR',
    mongoose,
    model: BlockchainProofBundle,
    id: String(bundle._id),
    proof: {
      sourceService: 'AISFOR',
      proofType: 'BLOCKCHAIN_PROOF_BUNDLE',
      actorId: 'aisfor-chain-e2e',
      externalProofId: bundle.bundleKey,
      idempotencyKey: `AISFOR:E2E:${bundle.bundleKey}`,
      contentHash,
      payloadHash,
      bundleHash,
      merkleRoot: contentHash,
      metadata: {
        bundleId: String(bundle._id),
        e2e: true,
        runId
      }
    },
    reload: () => BlockchainProofBundle.findById(bundle._id).lean()
  };
}

async function createAistalkMessage(runId) {
  const root = 'F:\\aistalk';
  const { req, mongoose } = await connectService(root, 'mongodb://127.0.0.1:27017/mariae');
  const ChatMessage = req('./models/ChatMessage');
  const roomId = new mongoose.Types.ObjectId();
  const senderId = new mongoose.Types.ObjectId();

  const message = await ChatMessage.create({
    roomId,
    senderId,
    text: `chain e2e ${runId}`,
    type: 'text'
  });

  return {
    service: 'AISTALK',
    mongoose,
    model: ChatMessage,
    id: String(message._id),
    proof: {
      sourceService: 'AISTALK',
      proofType: 'MESSAGE_CREATED',
      actorId: String(senderId),
      targetId: String(roomId),
      externalProofId: String(message._id),
      idempotencyKey: `AISTALK:E2E:MESSAGE:${message._id}`,
      contentHash: sha(`aistalk:${message._id}:${runId}`),
      metadata: {
        messageId: String(message._id),
        roomId: String(roomId),
        e2e: true,
        runId
      }
    },
    reload: () => ChatMessage.findById(message._id).lean()
  };
}

async function createAisgroupRecord(runId) {
  const root = 'E:\\aisfor-group';
  const { req, mongoose } = await connectService(root, 'mongodb://127.0.0.1:27017/aisfor');
  const EventRecord = req('./models/EventRecord');
  const groupId = new mongoose.Types.ObjectId();
  const authorId = new mongoose.Types.ObjectId();

  const record = await EventRecord.create({
    groupId,
    groupName: 'chain-e2e-group',
    title: `chain e2e ${runId}`,
    description: 'AISFOR-CHAIN end-to-end callback test',
    eventDate: new Date(),
    authorId,
    authorName: 'chain-e2e'
  });

  return {
    service: 'AISGROUP',
    mongoose,
    model: EventRecord,
    id: String(record._id),
    proof: {
      sourceService: 'AISGROUP',
      proofType: 'GROUP_ACTIVITY',
      actorId: String(authorId),
      targetId: String(groupId),
      externalProofId: String(record._id),
      idempotencyKey: `AISGROUP:E2E:EVENT:${record._id}`,
      contentHash: sha(`aisgroup:${record._id}:${runId}`),
      metadata: {
        recordId: String(record._id),
        groupId: String(groupId),
        activityType: 'EVENT_RECORD_CREATED',
        e2e: true,
        runId
      }
    },
    reload: () => EventRecord.findById(record._id).lean()
  };
}

async function createAismallOrder(runId) {
  const root = 'E:\\aismall';
  const { req, mongoose } = await connectService(root, 'mongodb://127.0.0.1:27017/aismall');
  const Order = req('./models/Order');
  const buyerId = new mongoose.Types.ObjectId();

  const order = await Order.create({
    buyerId,
    buyerName: 'chain-e2e',
    buyerPhone: '000-0000-0000',
    buyerEmail: 'chain-e2e@example.test',
    recvName: 'chain-e2e',
    recvPhone: '000-0000-0000',
    recvZip: '00000',
    recvAddr1: 'chain e2e',
    recvAddr2: '',
    totalAmount: 1,
    status: 'CREATED'
  });

  return {
    service: 'AISMALL',
    mongoose,
    model: Order,
    id: String(order._id),
    proof: {
      sourceService: 'AISMALL',
      proofType: 'MALL_ACTIVITY',
      actorId: String(buyerId),
      targetId: String(order._id),
      externalProofId: String(order._id),
      idempotencyKey: `AISMALL:E2E:ORDER:${order._id}`,
      contentHash: sha(`aismall:${order._id}:${runId}`),
      metadata: {
        orderId: String(order._id),
        activityType: 'ORDER_CREATED',
        e2e: true,
        runId
      }
    },
    reload: () => Order.findById(order._id).lean()
  };
}

async function finalizePendingBlocks() {
  await postJson(`${CHAIN_URL}/api/block/mine`, { limit: 100, reason: 'e2e-chain-callback-test' }).catch((err) => {
    if (err.data?.reason !== 'NO_PENDING_LEDGER_ITEMS') throw err;
  });

  const blockList = await getJson(`${CHAIN_URL}/api/block`);
  const pendingBlocks = (blockList.blocks || []).filter(block => block.status === 'PENDING_VALIDATION');
  const finalized = [];

  for (const block of pendingBlocks) {
    const result = await postJson(`${CHAIN_URL}/api/block/${block.height}/mock-finalize`, {});
    finalized.push(result.block);
  }

  return finalized;
}

async function main() {
  const runId = `${Date.now()}`;
  const contexts = [];
  const results = [];

  try {
    contexts.push(await createAisforBundle(runId));
    contexts.push(await createAistalkMessage(runId));
    contexts.push(await createAisgroupRecord(runId));
    contexts.push(await createAismallOrder(runId));

    for (const context of contexts) {
      const proofResult = await postJson(`${CHAIN_URL}/api/proof`, context.proof);
      results.push({
        service: context.service,
        sourceId: context.id,
        proofId: proofResult.proof?.id || null,
        proofCreated: proofResult.created
      });
    }

    const finalizedBlocks = await finalizePendingBlocks();

    for (const context of contexts) {
      const row = await context.reload();
      const result = results.find(item => item.service === context.service);
      result.chainConfirmed = row?.chainConfirmed === true;
      result.chainBlockHeight = row?.chainBlockHeight || 0;
      result.chainBlockHash = row?.chainBlockHash || '';
      result.chainTxHash = row?.chainTxHash || row?.anchorTxHash || '';
    }

    const failed = results.filter(result => !result.chainConfirmed);
    const callbackStats = await getJson(`${CHAIN_URL}/api/callback/stats/summary`);

    console.log(JSON.stringify({
      ok: failed.length === 0,
      runId,
      finalizedBlocks: finalizedBlocks.map(block => ({
        height: block.height,
        status: block.status,
        proofIds: block.proofIds
      })),
      callbackStats: callbackStats.stats,
      results
    }, null, 2));

    if (failed.length) process.exitCode = 1;
  } finally {
    await Promise.allSettled(contexts.map(context => context.mongoose.disconnect()));
  }
}

main().catch((err) => {
  console.error(JSON.stringify({
    ok: false,
    error: err.message,
    details: err.data || null
  }, null, 2));
  process.exitCode = 1;
});
