import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import { ConfigModule } from '@nestjs/config';
import { ApiModule } from '../../src/api/api.module';
import { ApplicationModule } from '../../src/application/application.module';
import { InfrastructureModule } from '../../src/infrastructure/infrastructure.module';
import { DomainModule } from '../../src/domain/domain.module';
import { PrismaService } from '../../src/infrastructure/persistence/prisma/prisma.service';
import { GlobalExceptionFilter } from '../../src/shared/filters/global-exception.filter';
import { MockPrismaService } from '../utils/mock-prisma.service';
import {
  generateServiceToken,
  generatePublicKey,
  createStoreSharePayload,
  createRetrieveSharePayload,
  createRevokeSharePayload,
  setupTestEnv,
  TEST_SERVICE_JWT_SECRET,
} from '../utils/test-utils';

describe('BackupShare E2E (Mock Database)', () => {
  let app: INestApplication;
  let mockPrisma: MockPrismaService;
  let serviceToken: string;

  beforeAll(async () => {
    setupTestEnv();

    mockPrisma = new MockPrismaService();

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          load: [
            () => ({
              app: { port: 3002, env: 'test' },
              security: {
                serviceJwtSecret: TEST_SERVICE_JWT_SECRET,
                allowedServices: ['identity-service', 'recovery-service'],
              },
              encryption: {
                key: process.env.BACKUP_ENCRYPTION_KEY,
                keyId: process.env.BACKUP_ENCRYPTION_KEY_ID,
              },
              rateLimit: { maxRetrievePerDay: 3 },
            }),
          ],
        }),
        DomainModule,
        InfrastructureModule,
        ApplicationModule,
        ApiModule,
      ],
    })
      .overrideProvider(PrismaService)
      .useValue(mockPrisma)
      .compile();

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

    await app.init();

    serviceToken = generateServiceToken('identity-service');
  });

  beforeEach(() => {
    mockPrisma.reset();
  });

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

  describe('Complete Workflow Tests', () => {
    it('should complete full lifecycle: store -> retrieve -> revoke', async () => {
      const publicKey = generatePublicKey('x');
      const storePayload = createStoreSharePayload({
        userId: '11111',
        accountSequence: 1111,
        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: '11111', 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: '11111', 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: '11111', 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: '22222', accountSequence: 2222, 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: '22222', publicKey }))
        .expect(200);

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

  describe('Validation Tests', () => {
    it('should reject store with missing userId', async () => {
      const payload = { ...createStoreSharePayload(), userId: undefined };
      delete (payload as any).userId;

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

    it('should reject store with missing accountSequence', async () => {
      const payload = { ...createStoreSharePayload(), accountSequence: undefined };
      delete (payload as any).accountSequence;

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

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

    it('should reject retrieve with missing recoveryToken', async () => {
      const payload = { ...createRetrieveSharePayload(), recoveryToken: undefined };
      delete (payload as any).recoveryToken;

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

    it('should reject revoke with invalid reason', async () => {
      await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .set('X-Service-Token', serviceToken)
        .send(createRevokeSharePayload({ reason: 'INVALID_REASON' }))
        .expect(400);
    });

    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 publicKey = generatePublicKey(String(i));
        const userId = String(30000 + i);

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

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

  describe('Authentication Tests', () => {
    it('should reject requests without token', async () => {
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .send(createStoreSharePayload())
        .expect(401);

      await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .send(createRetrieveSharePayload())
        .expect(401);

      await request(app.getHttpServer())
        .post('/backup-share/revoke')
        .send(createRevokeSharePayload())
        .expect(401);
    });

    it('should reject requests with malformed token', async () => {
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', 'not.a.valid.jwt')
        .send(createStoreSharePayload())
        .expect(401);
    });

    it('should reject requests from unknown services', async () => {
      const unknownToken = generateServiceToken('malicious-service');

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

    it('should reject tokens signed with wrong secret', async () => {
      const badToken = generateServiceToken('identity-service', 'wrong-secret');

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

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

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

    it('should return SHARE_ALREADY_EXISTS for duplicate store', async () => {
      const publicKey = generatePublicKey('d');
      const payload = createStoreSharePayload({ userId: '44444', accountSequence: 4444, publicKey });

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

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

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

    it('should return SHARE_NOT_ACTIVE for revoked share retrieval', async () => {
      const publicKey = generatePublicKey('e');

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

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

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

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

  describe('Optional Parameters Tests', () => {
    it('should accept custom threshold and totalParties', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(createStoreSharePayload({
          userId: '66666',
          accountSequence: 6666,
          publicKey: generatePublicKey('f'),
          threshold: 3,
          totalParties: 5,
        }))
        .expect(201);

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

    it('should accept deviceId in retrieve', async () => {
      const publicKey = generatePublicKey('g');

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

      const response = await request(app.getHttpServer())
        .post('/backup-share/retrieve')
        .set('X-Service-Token', serviceToken)
        .send(createRetrieveSharePayload({ userId: '77777', publicKey, deviceId: 'device-abc-123' }))
        .expect(200);

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

  describe('Response Structure Tests', () => {
    it('should return correct store response structure', async () => {
      const response = await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(createStoreSharePayload({
          userId: '80001',
          accountSequence: 80001,
          publicKey: generatePublicKey('s'),
        }))
        .expect(201);

      expect(response.body).toHaveProperty('success', true);
      expect(response.body).toHaveProperty('shareId');
      expect(response.body).toHaveProperty('message', 'Backup share stored successfully');
    });

    it('should return correct retrieve response structure', async () => {
      const publicKey = generatePublicKey('t');
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(createStoreSharePayload({ userId: '80002', accountSequence: 80002, publicKey }))
        .expect(201);

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

      expect(response.body).toHaveProperty('success', true);
      expect(response.body).toHaveProperty('encryptedShareData');
      expect(response.body).toHaveProperty('partyIndex', 2);
      expect(response.body).toHaveProperty('publicKey', publicKey);
    });

    it('should return correct revoke response structure', async () => {
      const publicKey = generatePublicKey('u');
      await request(app.getHttpServer())
        .post('/backup-share/store')
        .set('X-Service-Token', serviceToken)
        .send(createStoreSharePayload({ userId: '80003', accountSequence: 80003, publicKey }))
        .expect(201);

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

      expect(response.body).toHaveProperty('success', true);
      expect(response.body).toHaveProperty('message', 'Backup share revoked successfully');
    });

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

      expect(response.body).toHaveProperty('success', false);
      expect(response.body).toHaveProperty('error');
      expect(response.body).toHaveProperty('code');
      expect(response.body).toHaveProperty('timestamp');
      expect(response.body).toHaveProperty('path');
    });
  });
});
