import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import { AppModule } from '../../src/app.module';
import { PrismaService } from '../../src/infrastructure/persistence/prisma/prisma.service';
import {
  generateServiceToken,
  generatePublicKey,
  createStoreSharePayload,
  createRetrieveSharePayload,
  createRevokeSharePayload,
  TEST_SERVICE_JWT_SECRET,
} from '../utils/test-utils';

describe('BackupShare E2E (Real Database)', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let serviceToken: string;

  beforeAll(async () => {
    // Environment variables are loaded by jest-e2e-setup.ts
    process.env.SERVICE_JWT_SECRET = TEST_SERVICE_JWT_SECRET;

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

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );

    await app.init();

    prisma = app.get(PrismaService);
    serviceToken = generateServiceToken('identity-service');
  });

  beforeEach(async () => {
    // Clean database before each test
    if (prisma) {
      await prisma.shareAccessLog.deleteMany();
      await prisma.backupShare.deleteMany();
    }
  });

  afterAll(async () => {
    await app?.close();
  });

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

    it('GET /health/live should return alive', () => {
      return request(app.getHttpServer())
        .get('/health/live')
        .expect(200)
        .expect((res) => {
          expect(res.body.status).toBe('alive');
        });
    });
  });

  describe('POST /backup-share/store', () => {
    it('should store backup share successfully', async () => {
      const payload = createStoreSharePayload({
        userId: '10001',
        accountSequence: 10001,
        publicKey: generatePublicKey('a'),
      });

      const response = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(payload)
        .expect(201);

      expect(response.body.success).toBe(true);
      expect(response.body.shareId).toBeDefined();
      expect(response.body.message).toBe('Backup share stored successfully');

      // Verify data persisted to database
      const share = await prisma.backupShare.findUnique({
        where: { userId: BigInt(payload.userId) },
      });
      expect(share).not.toBeNull();
      expect(share?.publicKey).toBe(payload.publicKey);
      expect(share?.status).toBe('ACTIVE');
    });

    it('should reject duplicate share for same user', async () => {
      const payload = createStoreSharePayload({
        userId: '10002',
        accountSequence: 10002,
        publicKey: generatePublicKey('b'),
      });

      // First store
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(payload)
        .expect(201);

      // Duplicate attempt
      const response = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(payload)
        .expect(409);

      expect(response.body.success).toBe(false);
      expect(response.body.code).toBe('SHARE_ALREADY_EXISTS');
    });

    it('should reject request without service token', () => {
      return request(app.getHttpServer())
        .post('/backup-share/store')
        .send(createStoreSharePayload())
        .expect(401);
    });

    it('should reject request with invalid service token', () => {
      return request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', 'invalid-token')
        .send(createStoreSharePayload())
        .expect(401);
    });

    it('should reject request from unauthorized service', () => {
      const unauthorizedToken = generateServiceToken('unknown-service');

      return request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', unauthorizedToken)
        .send(createStoreSharePayload())
        .expect(401);
    });

    it('should validate required fields', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send({})
        .expect(400);

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

    it('should validate public key length', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send({
          ...createStoreSharePayload(),
          publicKey: 'short',
        })
        .expect(400);

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

  describe('POST /backup-share/retrieve', () => {
    const storePayload = {
      userId: '20001',
      accountSequence: 20001,
      publicKey: generatePublicKey('c'),
      encryptedShareData: 'test-encrypted-data',
    };

    beforeEach(async () => {
      // Store a share first
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(storePayload);
    });

    it('should retrieve backup share successfully', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
        }))
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.encryptedShareData).toBeDefined();
      expect(response.body.partyIndex).toBe(2);
      expect(response.body.publicKey).toBe(storePayload.publicKey);

      // Verify access log created for RETRIEVE action
      const accessLogs = await prisma.shareAccessLog.findMany({
        where: {
          userId: BigInt(storePayload.userId),
          action: 'RETRIEVE',
        },
      });
      expect(accessLogs.length).toBeGreaterThan(0);
      expect(accessLogs[0].action).toBe('RETRIEVE');
    });

    it('should return 404 for non-existent share', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({
          userId: '99999',
          publicKey: generatePublicKey('z'),
        }))
        .expect(404);

      expect(response.body.success).toBe(false);
      expect(response.body.code).toBe('SHARE_NOT_FOUND');
    });

    it('should increment access count on retrieve', async () => {
      // First retrieve
      await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
        }))
        .expect(200);

      // Second retrieve
      await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
        }))
        .expect(200);

      // Verify access count
      const share = await prisma.backupShare.findUnique({
        where: { userId: BigInt(storePayload.userId) },
      });
      expect(share?.accessCount).toBe(2);
    });
  });

  describe('POST /backup-share/revoke', () => {
    const storePayload = {
      userId: '30001',
      accountSequence: 30001,
      publicKey: generatePublicKey('e'),
      encryptedShareData: 'test-data',
    };

    beforeEach(async () => {
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(storePayload);
    });

    it('should revoke backup share successfully', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .set('X-Service-Token', serviceToken)
        .send(createRevokeSharePayload({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
          reason: 'ROTATION',
        }))
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.message).toBe('Backup share revoked successfully');

      // Verify share status in database
      const share = await prisma.backupShare.findUnique({
        where: { userId: BigInt(storePayload.userId) },
      });
      expect(share?.status).toBe('REVOKED');
    });

    it('should not allow retrieval of revoked share', async () => {
      // Revoke first
      await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .set('X-Service-Token', serviceToken)
        .send(createRevokeSharePayload({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
          reason: 'SECURITY_BREACH',
        }))
        .expect(200);

      // Try to retrieve
      const response = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
        }))
        .expect(400);

      expect(response.body.code).toBe('SHARE_NOT_ACTIVE');
    });

    it('should validate reason field', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .set('X-Service-Token', serviceToken)
        .send({
          userId: storePayload.userId,
          publicKey: storePayload.publicKey,
          reason: 'INVALID_REASON',
        })
        .expect(400);

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

    it('should accept all valid revoke reasons', async () => {
      const validReasons = ['ROTATION', 'ACCOUNT_CLOSED', 'SECURITY_BREACH', 'USER_REQUEST'];

      for (let i = 0; i < validReasons.length; i++) {
        const payload = {
          userId: String(40000 + i),
          accountSequence: 40000 + i,
          publicKey: generatePublicKey(String(i)),
          encryptedShareData: 'test-data',
        };

        // Store share
        await request(app.getHttpServer())
          .post('/backup-share/store')
          .set('X-Service-Token', serviceToken)
          .send(payload)
          .expect(201);

        // Revoke with valid reason
        const response = await request(app.getHttpServer())
          .post('/backup-share/revoke')
          .set('X-Service-Token', serviceToken)
          .send({
            userId: payload.userId,
            publicKey: payload.publicKey,
            reason: validReasons[i],
          })
          .expect(200);

        expect(response.body.success).toBe(true);
      }
    });
  });

  describe('Complete Workflow Tests', () => {
    it('should complete full lifecycle: store -> retrieve -> revoke', async () => {
      const publicKey = generatePublicKey('x');
      const storePayload = createStoreSharePayload({
        userId: '50001',
        accountSequence: 50001,
        publicKey,
      });

      // Step 1: Store
      const storeResponse = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(storePayload)
        .expect(201);

      expect(storeResponse.body.success).toBe(true);
      expect(storeResponse.body.shareId).toBeDefined();

      // Step 2: Retrieve
      const retrieveResponse = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({ userId: '50001', publicKey }))
        .expect(200);

      expect(retrieveResponse.body.success).toBe(true);
      expect(retrieveResponse.body.partyIndex).toBe(2);

      // Step 3: Revoke
      const revokeResponse = await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .set('X-Service-Token', serviceToken)
        .send(createRevokeSharePayload({ userId: '50001', publicKey, reason: 'ROTATION' }))
        .expect(200);

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

      // Step 4: Verify cannot retrieve after revoke
      await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({ userId: '50001', publicKey }))
        .expect(400);
    });

    it('should allow recovery-service to access shares', async () => {
      const recoveryToken = generateServiceToken('recovery-service');
      const publicKey = generatePublicKey('r');

      // Store with identity-service
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(createStoreSharePayload({ userId: '50002', accountSequence: 50002, publicKey }))
        .expect(201);

      // Retrieve with recovery-service
      const response = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', recoveryToken)
        .send(createRetrieveSharePayload({ userId: '50002', publicKey }))
        .expect(200);

      expect(response.body.success).toBe(true);
    });
  });

  describe('Data Persistence Tests', () => {
    it('should persist encrypted data correctly', async () => {
      const encryptedData = 'base64-encoded-encrypted-share-data-12345';
      const payload = createStoreSharePayload({
        userId: '60001',
        accountSequence: 60001,
        publicKey: generatePublicKey('p'),
        encryptedShareData: encryptedData,
      });

      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(payload)
        .expect(201);

      // Verify data in database (it should be double-encrypted)
      const share = await prisma.backupShare.findUnique({
        where: { userId: BigInt(payload.userId) },
      });

      expect(share).not.toBeNull();
      // The stored data should be different from input (encrypted)
      expect(share?.encryptedShareData).not.toBe(encryptedData);
      // But retrieving should give us back the original
      const retrieveResponse = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({
          userId: payload.userId,
          publicKey: payload.publicKey,
        }))
        .expect(200);

      expect(retrieveResponse.body.encryptedShareData).toBe(encryptedData);
    });

    it('should create audit logs for all operations', async () => {
      const publicKey = generatePublicKey('q');
      const userId = '60003';

      // Store
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(createStoreSharePayload({
          userId,
          accountSequence: 60003,
          publicKey,
        }))
        .expect(201);

      // Retrieve
      await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({ userId, publicKey }))
        .expect(200);

      // Revoke
      await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .set('X-Service-Token', serviceToken)
        .send(createRevokeSharePayload({ userId, publicKey }))
        .expect(200);

      // Check audit logs
      const logs = await prisma.shareAccessLog.findMany({
        where: { userId: BigInt(userId) },
        orderBy: { createdAt: 'asc' },
      });

      expect(logs.length).toBe(3);
      expect(logs[0].action).toBe('STORE');
      expect(logs[1].action).toBe('RETRIEVE');
      expect(logs[2].action).toBe('REVOKE');
    });
  });
});
