import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import * as jwt from 'jsonwebtoken';
import { AppModule } from '../src/app.module';
import { PrismaService } from '../src/infrastructure/persistence/prisma/prisma.service';

describe('Wallet Service (e2e) - Real Database', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let authToken: string;
  const testUserId = '99999'; // Unique test user ID
  const jwtSecret = process.env.JWT_SECRET || 'test-jwt-secret-key-for-e2e-testing';

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.setGlobalPrefix('api/v1');
    // ValidationPipe is needed for request validation
    // DomainExceptionFilter and TransformInterceptor are already provided globally by AppModule
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );

    prisma = app.get(PrismaService);
    await app.init();

    // Generate test JWT token
    authToken = jwt.sign(
      { sub: testUserId, seq: 1001 },
      jwtSecret,
      { expiresIn: '1h' },
    );

    // Clean up any existing test data
    await cleanupTestData();
  });

  afterAll(async () => {
    // Clean up test data
    await cleanupTestData();
    await app.close();
  });

  async function cleanupTestData() {
    try {
      await prisma.ledgerEntry.deleteMany({ where: { userId: BigInt(testUserId) } });
      await prisma.depositOrder.deleteMany({ where: { userId: BigInt(testUserId) } });
      await prisma.settlementOrder.deleteMany({ where: { userId: BigInt(testUserId) } });
      await prisma.walletAccount.deleteMany({ where: { userId: BigInt(testUserId) } });
    } catch (e) {
      console.log('Cleanup error (may be expected):', e);
    }
  }

  describe('Health Check', () => {
    it('/api/v1/health (GET) - should return health status', () => {
      return request(app.getHttpServer())
        .get('/api/v1/health')
        .expect(200)
        .expect(res => {
          expect(res.body.success).toBe(true);
          expect(res.body.data.status).toBe('ok');
          expect(res.body.data.service).toBe('wallet-service');
        });
    });
  });

  describe('Authentication', () => {
    it('/api/v1/wallet/my-wallet (GET) - should reject without auth', () => {
      return request(app.getHttpServer())
        .get('/api/v1/wallet/my-wallet')
        .expect(401);
    });

    it('/api/v1/wallet/my-wallet (GET) - should reject with invalid token', () => {
      return request(app.getHttpServer())
        .get('/api/v1/wallet/my-wallet')
        .set('Authorization', 'Bearer invalid_token')
        .expect(401);
    });
  });

  describe('Wallet Operations', () => {
    it('/api/v1/wallet/my-wallet (GET) - should return wallet info (creates wallet if not exists)', async () => {
      const res = await request(app.getHttpServer())
        .get('/api/v1/wallet/my-wallet')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveProperty('walletId');
      expect(res.body.data).toHaveProperty('userId', testUserId);
      expect(res.body.data).toHaveProperty('balances');
      expect(res.body.data).toHaveProperty('hashpower');
      expect(res.body.data).toHaveProperty('rewards');
      expect(res.body.data).toHaveProperty('status');
      expect(res.body.data.status).toBe('ACTIVE');
    });

    it('/api/v1/wallet/my-wallet (GET) - should have correct balance structure', async () => {
      const res = await request(app.getHttpServer())
        .get('/api/v1/wallet/my-wallet')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      const { balances } = res.body.data;
      expect(balances).toHaveProperty('usdt');
      expect(balances).toHaveProperty('dst');
      expect(balances).toHaveProperty('bnb');
      expect(balances).toHaveProperty('og');
      expect(balances).toHaveProperty('rwad');

      expect(balances.usdt).toHaveProperty('available');
      expect(balances.usdt).toHaveProperty('frozen');
      expect(typeof balances.usdt.available).toBe('number');
    });
  });

  describe('Ledger Operations', () => {
    it('/api/v1/wallet/ledger/my-ledger (GET) - should return ledger entries', async () => {
      const res = await request(app.getHttpServer())
        .get('/api/v1/wallet/ledger/my-ledger')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveProperty('data');
      expect(res.body.data).toHaveProperty('total');
      expect(res.body.data).toHaveProperty('page');
      expect(res.body.data).toHaveProperty('pageSize');
      expect(res.body.data).toHaveProperty('totalPages');
      expect(Array.isArray(res.body.data.data)).toBe(true);
    });

    it('/api/v1/wallet/ledger/my-ledger (GET) - should support pagination', async () => {
      const res = await request(app.getHttpServer())
        .get('/api/v1/wallet/ledger/my-ledger?page=1&pageSize=5')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(res.body.data.page).toBe(1);
      expect(res.body.data.pageSize).toBe(5);
    });

    it('/api/v1/wallet/ledger/my-ledger (GET) - should reject invalid pagination', async () => {
      await request(app.getHttpServer())
        .get('/api/v1/wallet/ledger/my-ledger?page=0')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(400);
    });
  });

  describe('Deposit Operations (Internal API)', () => {
    const testTxHash = `test_tx_e2e_${Date.now()}`;

    it('/api/v1/wallet/deposit (POST) - should process deposit', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/wallet/deposit')
        .send({
          userId: testUserId,
          amount: 100,
          chainType: 'KAVA',
          txHash: testTxHash,
        })
        .expect(201);

      expect(res.body.success).toBe(true);
      expect(res.body.data.message).toBe('Deposit processed successfully');

      // Verify balance was updated
      const walletRes = await request(app.getHttpServer())
        .get('/api/v1/wallet/my-wallet')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(walletRes.body.data.balances.usdt.available).toBe(100);
    });

    it('/api/v1/wallet/deposit (POST) - should reject duplicate transaction', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/wallet/deposit')
        .send({
          userId: testUserId,
          amount: 50,
          chainType: 'KAVA',
          txHash: testTxHash, // Same txHash as before
        })
        .expect(409);

      expect(res.body.success).toBe(false);
      expect(res.body.code).toBe('DUPLICATE_TRANSACTION');
    });

    it('/api/v1/wallet/deposit (POST) - should validate request body', async () => {
      await request(app.getHttpServer())
        .post('/api/v1/wallet/deposit')
        .send({
          userId: testUserId,
          amount: -100, // Invalid negative amount
          chainType: 'KAVA',
          txHash: 'invalid_tx',
        })
        .expect(400);
    });

    it('/api/v1/wallet/deposit (POST) - should validate chain type', async () => {
      await request(app.getHttpServer())
        .post('/api/v1/wallet/deposit')
        .send({
          userId: testUserId,
          amount: 100,
          chainType: 'INVALID_CHAIN',
          txHash: 'tx_invalid_chain',
        })
        .expect(400);
    });

    it('/api/v1/wallet/deposit (POST) - should process BSC deposit', async () => {
      const bscTxHash = `test_bsc_tx_${Date.now()}`;
      const res = await request(app.getHttpServer())
        .post('/api/v1/wallet/deposit')
        .send({
          userId: testUserId,
          amount: 50,
          chainType: 'BSC',
          txHash: bscTxHash,
        })
        .expect(201);

      expect(res.body.success).toBe(true);

      // Verify balance was updated (should be 100 + 50 = 150)
      const walletRes = await request(app.getHttpServer())
        .get('/api/v1/wallet/my-wallet')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(walletRes.body.data.balances.usdt.available).toBe(150);
    });
  });

  describe('Ledger After Deposits', () => {
    it('should have ledger entries after deposits', async () => {
      const res = await request(app.getHttpServer())
        .get('/api/v1/wallet/ledger/my-ledger')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(res.body.data.total).toBeGreaterThanOrEqual(2); // At least 2 deposits
      expect(res.body.data.data.length).toBeGreaterThanOrEqual(2);

      // Check ledger entry structure
      const entry = res.body.data.data[0];
      expect(entry).toHaveProperty('id');
      expect(entry).toHaveProperty('entryType');
      expect(entry).toHaveProperty('amount');
      expect(entry).toHaveProperty('assetType');
      expect(entry).toHaveProperty('createdAt');
    });
  });

  describe('Settlement Operations', () => {
    it('/api/v1/wallet/claim-rewards (POST) - should handle no pending rewards', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/wallet/claim-rewards')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(400);

      expect(res.body.success).toBe(false);
    });

    it('/api/v1/wallet/settle (POST) - should validate settle currency', async () => {
      await request(app.getHttpServer())
        .post('/api/v1/wallet/settle')
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          usdtAmount: 10,
          settleCurrency: 'INVALID',
        })
        .expect(400);
    });

    it('/api/v1/wallet/settle (POST) - should validate amount', async () => {
      await request(app.getHttpServer())
        .post('/api/v1/wallet/settle')
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          usdtAmount: -10,
          settleCurrency: 'BNB',
        })
        .expect(400);
    });
  });

  describe('Error Handling', () => {
    it('should return 404 for non-existent routes', () => {
      return request(app.getHttpServer())
        .get('/api/v1/non-existent')
        .expect(404);
    });

    it('should handle malformed JSON', () => {
      return request(app.getHttpServer())
        .post('/api/v1/wallet/deposit')
        .set('Content-Type', 'application/json')
        .send('{ invalid json }')
        .expect(400);
    });
  });

  describe('Database Integrity', () => {
    it('should persist wallet data correctly', async () => {
      const wallet = await prisma.walletAccount.findFirst({
        where: { userId: BigInt(testUserId) },
      });

      expect(wallet).not.toBeNull();
      expect(wallet?.status).toBe('ACTIVE');
      expect(Number(wallet?.usdtAvailable)).toBe(150); // 100 + 50 from deposits
    });

    it('should persist ledger entries correctly', async () => {
      const entries = await prisma.ledgerEntry.findMany({
        where: { userId: BigInt(testUserId) },
        orderBy: { createdAt: 'desc' },
      });

      expect(entries.length).toBeGreaterThanOrEqual(2);

      // Check that deposits are recorded
      const depositEntries = entries.filter(e =>
        e.entryType === 'DEPOSIT_KAVA' || e.entryType === 'DEPOSIT_BSC'
      );
      expect(depositEntries.length).toBe(2);
    });

    it('should persist deposit orders correctly', async () => {
      const deposits = await prisma.depositOrder.findMany({
        where: { userId: BigInt(testUserId) },
      });

      expect(deposits.length).toBe(2);
      deposits.forEach(deposit => {
        expect(deposit.status).toBe('CONFIRMED');
      });
    });
  });
});
