import { Test, TestingModule } from '@nestjs/testing';
import {
  INestApplication,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import * as request from 'supertest';
import { OpenfinanceExcludedTransactionsController } from '../src/openfinance-excluded-transactions/openfinance-excluded-transactions.controller';
import { OpenfinanceExcludedTransactionsService } from '../src/openfinance-excluded-transactions/openfinance-excluded-transactions.service';
import { ApiKeyGuard } from '../src/common/guards/api-key/api-key.guard';
import { ConfigModule } from '@nestjs/config';

// Mock do service de OpenFinance Excluded Transactions
const mockOpenfinanceExcludedTransactionsService = {
  create: jest.fn(),
  findAllByClient: jest.fn(),
  delete: jest.fn(),
};

// Mock do API Key Guard
const mockApiKeyGuard = {
  canActivate: jest.fn(() => true),
};

describe('OpenFinance Excluded Transactions Module (e2e)', () => {
  let app: INestApplication;
  const API_KEY = 'JOqyUcP45H875xmuI2gT3H9dDK42I2Wt';

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          ignoreEnvFile: true,
          load: [
            () => ({
              API_KEY: 'JOqyUcP45H875xmuI2gT3H9dDK42I2Wt',
            }),
          ],
        }),
      ],
      controllers: [OpenfinanceExcludedTransactionsController],
      providers: [
        {
          provide: OpenfinanceExcludedTransactionsService,
          useValue: mockOpenfinanceExcludedTransactionsService,
        },
        {
          provide: ApiKeyGuard,
          useValue: mockApiKeyGuard,
        },
      ],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

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

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('POST /openfinance-excluded-transactions', () => {
    it('deve criar uma nova transação excluída do OpenFinance', () => {
      const newExcludedTransaction = {
        client_id: 'client_123',
        transaction_openfinance_id: 'transaction_456',
        reason: 'Transação duplicada',
        excluded_at: '2024-01-15T10:00:00Z',
        metadata: {
          original_amount: 100.0,
          original_description: 'Transferência duplicada',
        },
      };

      const createdTransaction = {
        id: 'excluded_transaction_1',
        ...newExcludedTransaction,
        created_at: '2024-01-15T10:00:00Z',
        updated_at: '2024-01-15T10:00:00Z',
      };

      mockOpenfinanceExcludedTransactionsService.create.mockResolvedValue(
        createdTransaction,
      );

      return request(app.getHttpServer())
        .post('/openfinance-excluded-transactions')
        .set('x-api-key', API_KEY)
        .send(newExcludedTransaction)
        .expect(201)
        .expect((res) => {
          expect(res.body).toEqual(createdTransaction);
          expect(
            mockOpenfinanceExcludedTransactionsService.create,
          ).toHaveBeenCalledWith(newExcludedTransaction);
        });
    });

    it('deve retornar erro 400 para dados inválidos', () => {
      const invalidTransaction = {
        // client_id ausente
        transaction_openfinance_id: 'transaction_456',
        reason: 'Transação duplicada',
      };

      mockOpenfinanceExcludedTransactionsService.create.mockRejectedValue(
        new BadRequestException('client_id is required'),
      );

      return request(app.getHttpServer())
        .post('/openfinance-excluded-transactions')
        .set('x-api-key', API_KEY)
        .send(invalidTransaction)
        .expect(400);
    });

    it('deve retornar erro 401 sem API key', () => {
      mockApiKeyGuard.canActivate.mockReturnValue(false);

      return request(app.getHttpServer())
        .post('/openfinance-excluded-transactions')
        .send({
          client_id: 'client_123',
          transaction_openfinance_id: 'transaction_456',
          reason: 'Transação duplicada',
        })
        .expect(401);
    });

    it('deve criar transação excluída com dados mínimos', () => {
      const minimalTransaction = {
        client_id: 'client_123',
        transaction_openfinance_id: 'transaction_789',
        reason: 'Transação inválida',
      };

      const createdTransaction = {
        id: 'excluded_transaction_2',
        ...minimalTransaction,
        excluded_at: '2024-01-15T10:00:00Z',
        created_at: '2024-01-15T10:00:00Z',
        updated_at: '2024-01-15T10:00:00Z',
      };

      mockOpenfinanceExcludedTransactionsService.create.mockResolvedValue(
        createdTransaction,
      );

      return request(app.getHttpServer())
        .post('/openfinance-excluded-transactions')
        .set('x-api-key', API_KEY)
        .send(minimalTransaction)
        .expect(201)
        .expect((res) => {
          expect(res.body).toEqual(createdTransaction);
          expect(
            mockOpenfinanceExcludedTransactionsService.create,
          ).toHaveBeenCalledWith(minimalTransaction);
        });
    });
  });

  describe('GET /openfinance-excluded-transactions', () => {
    it('deve listar transações excluídas por client_id', () => {
      const mockExcludedTransactions = [
        {
          id: 'excluded_transaction_1',
          client_id: 'client_123',
          transaction_openfinance_id: 'transaction_456',
          reason: 'Transação duplicada',
          excluded_at: '2024-01-15T10:00:00Z',
          created_at: '2024-01-15T10:00:00Z',
        },
        {
          id: 'excluded_transaction_2',
          client_id: 'client_123',
          transaction_openfinance_id: 'transaction_789',
          reason: 'Transação inválida',
          excluded_at: '2024-01-15T11:00:00Z',
          created_at: '2024-01-15T11:00:00Z',
        },
      ];

      mockOpenfinanceExcludedTransactionsService.findAllByClient.mockResolvedValue(
        mockExcludedTransactions,
      );

      return request(app.getHttpServer())
        .get('/openfinance-excluded-transactions?client_id=client_123')
        .set('x-api-key', API_KEY)
        .expect(200)
        .expect((res) => {
          expect(res.body).toEqual(mockExcludedTransactions);
          expect(
            mockOpenfinanceExcludedTransactionsService.findAllByClient,
          ).toHaveBeenCalledWith('client_123');
        });
    });

    it('deve retornar array vazio para client_id inexistente', () => {
      const emptyResult = [];

      mockOpenfinanceExcludedTransactionsService.findAllByClient.mockResolvedValue(
        emptyResult,
      );

      return request(app.getHttpServer())
        .get('/openfinance-excluded-transactions?client_id=client_inexistente')
        .set('x-api-key', API_KEY)
        .expect(200)
        .expect((res) => {
          expect(res.body).toEqual(emptyResult);
          expect(
            mockOpenfinanceExcludedTransactionsService.findAllByClient,
          ).toHaveBeenCalledWith('client_inexistente');
        });
    });

    it('deve retornar erro 400 quando client_id não é fornecido', () => {
      mockOpenfinanceExcludedTransactionsService.findAllByClient.mockRejectedValue(
        new BadRequestException('client_id is required'),
      );

      return request(app.getHttpServer())
        .get('/openfinance-excluded-transactions')
        .set('x-api-key', API_KEY)
        .expect(400);
    });

    it('deve retornar erro 401 sem API key', () => {
      mockApiKeyGuard.canActivate.mockReturnValue(false);

      return request(app.getHttpServer())
        .get('/openfinance-excluded-transactions?client_id=client_123')
        .expect(401);
    });

    it('deve listar múltiplas transações excluídas', () => {
      const mockMultipleTransactions = [
        {
          id: 'excluded_transaction_1',
          client_id: 'client_123',
          transaction_openfinance_id: 'transaction_1',
          reason: 'Duplicada',
        },
        {
          id: 'excluded_transaction_2',
          client_id: 'client_123',
          transaction_openfinance_id: 'transaction_2',
          reason: 'Inválida',
        },
        {
          id: 'excluded_transaction_3',
          client_id: 'client_123',
          transaction_openfinance_id: 'transaction_3',
          reason: 'Erro de processamento',
        },
      ];

      mockOpenfinanceExcludedTransactionsService.findAllByClient.mockResolvedValue(
        mockMultipleTransactions,
      );

      return request(app.getHttpServer())
        .get('/openfinance-excluded-transactions?client_id=client_123')
        .set('x-api-key', API_KEY)
        .expect(200)
        .expect((res) => {
          expect(res.body).toEqual(mockMultipleTransactions);
          expect(res.body).toHaveLength(3);
          expect(
            mockOpenfinanceExcludedTransactionsService.findAllByClient,
          ).toHaveBeenCalledWith('client_123');
        });
    });
  });

  describe('DELETE /openfinance-excluded-transactions', () => {
    it('deve remover uma transação excluída', () => {
      const deleteResponse = {
        message: 'Transação excluída removida com sucesso',
        transaction_openfinance_id: 'transaction_456',
        client_id: 'client_123',
      };

      mockOpenfinanceExcludedTransactionsService.delete.mockResolvedValue(
        deleteResponse,
      );

      return request(app.getHttpServer())
        .delete(
          '/openfinance-excluded-transactions?transaction_openfinance_id=transaction_456',
        )
        .set('x-api-key', API_KEY)
        .expect(200)
        .expect((res) => {
          expect(res.body).toEqual(deleteResponse);
          expect(
            mockOpenfinanceExcludedTransactionsService.delete,
          ).toHaveBeenCalledWith('transaction_456');
        });
    });

    it('deve retornar erro 404 para transação inexistente', () => {
      mockOpenfinanceExcludedTransactionsService.delete.mockRejectedValue(
        new NotFoundException('Excluded transaction not found'),
      );

      return request(app.getHttpServer())
        .delete(
          '/openfinance-excluded-transactions?transaction_openfinance_id=transaction_inexistente',
        )
        .set('x-api-key', API_KEY)
        .expect(404);
    });

    it('deve retornar erro 400 quando transaction_openfinance_id não é fornecido', () => {
      return request(app.getHttpServer())
        .delete('/openfinance-excluded-transactions')
        .set('x-api-key', API_KEY)
        .expect(400);
    });

    it('deve retornar erro 401 sem API key', () => {
      mockApiKeyGuard.canActivate.mockReturnValue(false);

      return request(app.getHttpServer())
        .delete(
          '/openfinance-excluded-transactions?transaction_openfinance_id=transaction_456',
        )
        .expect(401);
    });
  });

  describe('Fluxo completo de transações excluídas', () => {
    it('deve executar fluxo completo: criar, listar e remover', async () => {
      // 1. Criar transação excluída
      const newTransaction = {
        client_id: 'client_123',
        transaction_openfinance_id: 'transaction_fluxo',
        reason: 'Teste de fluxo completo',
      };

      const createdTransaction = {
        id: 'excluded_transaction_fluxo',
        ...newTransaction,
        excluded_at: '2024-01-15T10:00:00Z',
        created_at: '2024-01-15T10:00:00Z',
        updated_at: '2024-01-15T10:00:00Z',
      };

      mockOpenfinanceExcludedTransactionsService.create.mockResolvedValue(
        createdTransaction,
      );

      const createResponse = await request(app.getHttpServer())
        .post('/openfinance-excluded-transactions')
        .set('x-api-key', API_KEY)
        .send(newTransaction)
        .expect(201);

      expect(createResponse.body).toEqual(createdTransaction);

      // 2. Listar transações excluídas
      const mockList = [createdTransaction];
      mockOpenfinanceExcludedTransactionsService.findAllByClient.mockResolvedValue(
        mockList,
      );

      const listResponse = await request(app.getHttpServer())
        .get('/openfinance-excluded-transactions?client_id=client_123')
        .set('x-api-key', API_KEY)
        .expect(200);

      expect(listResponse.body).toEqual(mockList);

      // 3. Remover transação excluída
      const deleteResponse = {
        message: 'Transação excluída removida com sucesso',
        transaction_openfinance_id: 'transaction_fluxo',
        client_id: 'client_123',
      };

      mockOpenfinanceExcludedTransactionsService.delete.mockResolvedValue(
        deleteResponse,
      );

      const removeResponse = await request(app.getHttpServer())
        .delete(
          '/openfinance-excluded-transactions?transaction_openfinance_id=transaction_fluxo',
        )
        .set('x-api-key', API_KEY)
        .expect(200);

      expect(removeResponse.body).toEqual(deleteResponse);
    });
  });
});
