import { Injectable, BadRequestException } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { WalletService } from '../wallet/wallet.service';
import * as crypto from 'crypto';

@Injectable()
export class WebhookService {
  constructor(
    private dataSource: DataSource,
    private walletService: WalletService,
  ) {}

  async handlePayUCallback(payload: any) {
    // 1. Extract Parameters
    const {
      status,
      txnid,
      amount,
      productinfo,
      firstname,
      email,
      udf1,
      udf2,
      udf3,
      udf4,
      udf5,
      mihpayid,
      hash,
      key,
    } = payload;

    const salt = process.env.PAYU_SALT || 'test_salt_v1';

    // 2. Verify Hash (Reverse Hash)
    // Formula: sha512(salt|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key)
    const hashString = `${salt}|${status}||||||${udf5 || ''}|${udf4 || ''}|${udf3 || ''}|${udf2 || ''}|${udf1 || ''}|${email}|${firstname}|${productinfo}|${amount}|${txnid}|${key}`;
    const calculatedHash = crypto
      .createHash('sha512')
      .update(hashString)
      .digest('hex');

    if (calculatedHash !== hash) {
      console.error('Invalid Hash', {
        expected: calculatedHash,
        received: hash,
        string: hashString,
      });
      throw new BadRequestException('Invalid checksum');
    }

    // 3. Transactional Processing (State Transition & Idempotency Law)
    await this.dataSource.transaction(async (manager) => {
      // A. Lock and Fetch intent
      // txnid = gateway_order_id (the unique ID for THIS attempt)
      const [paymentRecord] = await manager.query(
        'SELECT * FROM payments WHERE gateway_order_id = ? FOR UPDATE',
        [txnid],
      );

      if (!paymentRecord) {
        console.error(`Order ${txnid} not found in DB`);
        return;
      }

      // State Transition Law: initiated -> processing -> success | failed
      // No backward transitions. success/failed are terminal for a txnid.
      if (
        paymentRecord.status === 'success' ||
        paymentRecord.status === 'failed'
      ) {
        console.log(
          `Payment ${txnid} already terminal: ${paymentRecord.status}`,
        );
        return; // Idempotent
      }

      const dbStatus = status === 'success' ? 'success' : 'failed';

      // B. Atomic Status Update with Guard
      const updateResult = await manager.query(
        `UPDATE payments 
                 SET status = ?, gateway_payment_id = ?, raw_response = ? 
                 WHERE id = ? AND status NOT IN ('success', 'failed')`,
        [dbStatus, mihpayid, JSON.stringify(payload), paymentRecord.id],
      );

      // If no row was updated, someone else got here first
      if (updateResult.affectedRows === 0) {
        console.log(
          `Race condition or terminal state reached for intent ${paymentRecord.id}`,
        );
        return;
      }

      // Stop if not success
      if (dbStatus !== 'success') return;

      // C. Fulfill Purchase (Atomic & Idempotent)
      // Using the intent ID and user ID for fulfillment logic
      const entityType = udf1;
      const entityId = udf2;
      const couponId = udf3 ? Number(udf3) : null;
      const userId = paymentRecord.user_id;

      if (entityType === 'plan') {
        const query = isNaN(Number(entityId))
          ? 'SELECT * FROM plans WHERE code = ?'
          : 'SELECT * FROM plans WHERE id = ?';
        const [plan] = await manager.query(query, [entityId]);
        if (plan) {
          const expiresAt = new Date();
          const validity =
            plan.validity_days !== undefined &&
            plan.validity_days !== null &&
            !isNaN(Number(plan.validity_days))
              ? Number(plan.validity_days)
              : 30;
          expiresAt.setDate(expiresAt.getDate() + validity);

          // 1. Credit Wallet (Ledger Law: Immutable Transactions)
          if (plan.job_credits > 0) {
            await this.walletService.creditWallet(
              userId,
              'job',
              plan.job_credits,
              `Plan Purchase: ${plan.name}`,
              'payment',
              paymentRecord.id,
              manager,
              expiresAt,
            );
          }
          if (plan.database_credits > 0) {
            await this.walletService.creditWallet(
              userId,
              'database',
              plan.database_credits,
              `Plan Purchase: ${plan.name}`,
              'payment',
              paymentRecord.id,
              manager,
              expiresAt,
            );
          }
          if (plan.ai_credits > 0) {
            await this.walletService.creditWallet(
              userId,
              'ai',
              plan.ai_credits,
              `Plan Purchase: ${plan.name}`,
              'payment',
              paymentRecord.id,
              manager,
              expiresAt,
            );
          }

          // 2. Subscription
          const startsAt = new Date();
          const endsAt = new Date();
          endsAt.setDate(endsAt.getDate() + validity);

          await manager.query(
            `INSERT INTO user_subscriptions 
                      (user_id, plan_id, starts_at, ends_at, status)
                      VALUES (?, ?, ?, ?, 'active')`,
            [userId, plan.id, startsAt, endsAt],
          );
        }
      } else if (entityType === 'job_package') {
        const query = isNaN(Number(entityId))
          ? 'SELECT * FROM job_packages WHERE code = ?'
          : 'SELECT * FROM job_packages WHERE id = ?';
        const [pkg] = await manager.query(query, [entityId]);
        if (pkg) {
          if (pkg.job_credits > 0) {
            await this.walletService.creditWallet(
              userId,
              'job',
              pkg.job_credits,
              `Package: ${pkg.name}`,
              'payment',
              paymentRecord.id,
              manager,
            );
          }
        }
      }

      // D. Record Coupon Redemption
      if (couponId) {
        await manager.query(
          `INSERT INTO coupon_redemptions (coupon_id, user_id, payment_id, discount_amount)
                 VALUES (?, ?, ?, ?)`,
          [couponId, userId, paymentRecord.id, 0], // logic for precise amount can be added if stored in raw_request
        );
      }

      // E. Generate Invoice (Auditable Law)
      await manager.query(
        `INSERT INTO invoices 
             (user_id, payment_id, invoice_number, seller_gstin, base_amount, gst_amount, total_amount, status)
             VALUES (?, ?, ?, ?, ?, ?, ?, 'generated')`,
        [
          userId,
          paymentRecord.id,
          `INV-${Date.now()}-${paymentRecord.id}`, // Unique invoice number
          'GSTIN_PLACEHOLDER',
          paymentRecord.base_amount,
          paymentRecord.gst_amount,
          paymentRecord.total_amount,
        ],
      );
    });

    return { status: 'processed' };
  }
}
