import 'dotenv/config';
import 'reflect-metadata';
import { initializeDatabase, AppDataSource } from '../infrastructure/persistence/data-source';
import { PinoLogger } from '../infrastructure/logging/pino-logger';
import { TransactionEntity } from '../infrastructure/persistence/entities/transaction.entity';
import { TokenEntity } from '../infrastructure/persistence/entities/token.entity';
import { isTokenTransferPath } from '../domain/value-objects/transaction-data';

const DRY_RUN = process.env.DRY_RUN === 'true';

const run = async (): Promise<void> => {
  await initializeDatabase();

  const logger = new PinoLogger();

  const transactionRepository = AppDataSource.getRepository(TransactionEntity);
  const tokenRepository = AppDataSource.getRepository(TokenEntity);

  const [transactions, tokens] = await Promise.all([
    transactionRepository.find(),
    tokenRepository.find(),
  ]);

  const tokenById = new Map(tokens.map((t) => [t.id, t]));

  logger.info('Starting transaction network healing', {
    dryRun: DRY_RUN,
    transactions: transactions.length,
    tokens: tokens.length,
  });

  const resolveExpectedNetworkId = (entity: TransactionEntity): string | null => {
    if (entity.interactedWith) {
      const token = tokenById.get(entity.interactedWith);
      if (token) {
        return token.networkId;
      }
    }

    for (const path of entity.data) {
      // Swap-style token transfer paths carry token ids in from/to.
      if (isTokenTransferPath(path)) {
        const token = tokenById.get(path.from) ?? tokenById.get(path.to);
        if (token) {
          return token.networkId;
        }
      }
    }

    return null;
  };

  let healed = 0;
  let skipped = 0;
  let failed = 0;
  let undetermined = 0;

  for (const entity of transactions) {
    const expectedNetworkId = resolveExpectedNetworkId(entity);

    if (!expectedNetworkId) {
      undetermined++;
      logger.warn('Could not determine expected network for transaction', {
        id: entity.id,
        currentNetworkId: entity.networkId,
      });
      continue;
    }

    if (entity.networkId === expectedNetworkId) {
      skipped++;
      continue;
    }

    const oldNetworkId = entity.networkId;

    if (DRY_RUN) {
      logger.info('[DRY RUN] Would heal transaction network', {
        id: entity.id,
        hash: entity.hash,
        oldNetworkId,
        newNetworkId: expectedNetworkId,
      });
      healed++;
      continue;
    }

    entity.networkId = expectedNetworkId;
    entity.updatedAt = new Date();

    try {
      await transactionRepository.save(entity);
      logger.info('Healed transaction network', {
        id: entity.id,
        hash: entity.hash,
        oldNetworkId,
        newNetworkId: expectedNetworkId,
      });
      healed++;
    } catch (error) {
      logger.error('Failed to heal transaction network', {
        id: entity.id,
        hash: entity.hash,
        oldNetworkId,
        newNetworkId: expectedNetworkId,
        error,
      });
      failed++;
      entity.networkId = oldNetworkId;
    }
  }

  logger.info('Transaction network healing complete', {
    dryRun: DRY_RUN,
    transactions: transactions.length,
    healed,
    skipped,
    failed,
    undetermined,
  });

  process.exit(failed > 0 ? 1 : 0);
};

run().catch((error) => {
  // eslint-disable-next-line no-console
  console.error('Transaction network healing failed', error);
  process.exit(1);
});
