import { jest, describe, it, expect, beforeEach } from '@jest/globals';
import { Response, NextFunction } from 'express';
import { createMockPartner } from '../../../__tests__/helpers.js';
import { AuthenticatedRequest } from '../../../shared/types/index.js';

// Mock the service
const mockFindAll = jest.fn();
const mockFindById = jest.fn();
const mockCreate = jest.fn();
const mockUpdate = jest.fn();
const mockDelete = jest.fn();
const mockFindCustomers = jest.fn();
const mockFindSuppliers = jest.fn();

jest.mock('../partners.service.js', () => ({
  partnersService: {
    findAll: (...args: any[]) => mockFindAll(...args),
    findById: (...args: any[]) => mockFindById(...args),
    create: (...args: any[]) => mockCreate(...args),
    update: (...args: any[]) => mockUpdate(...args),
    delete: (...args: any[]) => mockDelete(...args),
    findCustomers: (...args: any[]) => mockFindCustomers(...args),
    findSuppliers: (...args: any[]) => mockFindSuppliers(...args),
  },
}));

// Import after mocking
import { partnersController } from '../partners.controller.js';

describe('PartnersController', () => {
  let mockReq: Partial<AuthenticatedRequest>;
  let mockRes: Partial<Response>;
  let mockNext: NextFunction;
  const tenantId = 'test-tenant-uuid';
  const userId = 'test-user-uuid';

  beforeEach(() => {
    jest.clearAllMocks();
    mockReq = {
      user: {
        id: userId,
        userId,
        tenantId,
        email: 'test@test.com',
        role: 'admin',
      } as any,
      params: {},
      query: {},
      body: {},
    };
    mockRes = {
      status: jest.fn().mockReturnThis() as any,
      json: jest.fn() as any,
    };
    mockNext = jest.fn();
  });

  describe('findAll', () => {
    it('should return paginated partners', async () => {
      const mockPartners = {
        data: [createMockPartner()],
        total: 1,
      };
      mockFindAll.mockResolvedValue(mockPartners);
      mockReq.query = { page: '1', limit: '20' };

      await partnersController.findAll(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockFindAll).toHaveBeenCalledWith(tenantId, expect.objectContaining({
        page: 1,
        limit: 20,
      }));
      expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({
        success: true,
        data: mockPartners.data,
      }));
    });

    it('should apply filters from query params', async () => {
      mockFindAll.mockResolvedValue({ data: [], total: 0 });
      mockReq.query = { search: 'test', partnerType: 'customer', isActive: 'true' };

      await partnersController.findAll(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockFindAll).toHaveBeenCalledWith(tenantId, expect.objectContaining({
        search: 'test',
        partnerType: 'customer',
        isActive: true,
      }));
    });

    it('should call next with error on invalid query', async () => {
      mockReq.query = { page: 'invalid' };

      await partnersController.findAll(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockNext).toHaveBeenCalled();
    });
  });

  describe('findById', () => {
    it('should return partner when found', async () => {
      const mockPartner = createMockPartner();
      mockFindById.mockResolvedValue(mockPartner);
      mockReq.params = { id: 'partner-uuid-1' };

      await partnersController.findById(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockFindById).toHaveBeenCalledWith('partner-uuid-1', tenantId);
      expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({
        success: true,
        data: mockPartner,
      }));
    });

    it('should call next with error when partner not found', async () => {
      mockFindById.mockRejectedValue(new Error('Not found'));
      mockReq.params = { id: 'nonexistent' };

      await partnersController.findById(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockNext).toHaveBeenCalled();
    });
  });

  describe('create', () => {
    it('should create partner successfully', async () => {
      const mockPartner = createMockPartner();
      mockCreate.mockResolvedValue(mockPartner);
      mockReq.body = {
        code: 'PART-001',
        displayName: 'New Partner',
        email: 'new@partner.com',
        partnerType: 'customer',
      };

      await partnersController.create(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          code: 'PART-001',
          displayName: 'New Partner',
          email: 'new@partner.com',
          partnerType: 'customer',
        }),
        tenantId,
        userId
      );
      expect(mockRes.status).toHaveBeenCalledWith(201);
    });

    it('should validate required fields', async () => {
      mockReq.body = {}; // Missing required code

      await partnersController.create(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockNext).toHaveBeenCalled();
      expect(mockCreate).not.toHaveBeenCalled();
    });

    it('should accept snake_case fields', async () => {
      const mockPartner = createMockPartner();
      mockCreate.mockResolvedValue(mockPartner);
      mockReq.body = {
        code: 'PART-002',
        display_name: 'Snake Case Partner',
        partner_type: 'supplier',
      };

      await partnersController.create(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockCreate).toHaveBeenCalled();
    });
  });

  describe('update', () => {
    it('should update partner successfully', async () => {
      const mockPartner = createMockPartner({ displayName: 'Updated Name' });
      mockUpdate.mockResolvedValue(mockPartner);
      mockReq.params = { id: 'partner-uuid-1' };
      mockReq.body = { displayName: 'Updated Name' };

      await partnersController.update(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockUpdate).toHaveBeenCalledWith(
        'partner-uuid-1',
        expect.objectContaining({ displayName: 'Updated Name' }),
        tenantId,
        userId
      );
    });

    it('should call next with error on invalid data', async () => {
      mockReq.params = { id: 'partner-uuid-1' };
      mockReq.body = { email: 'not-an-email' };

      await partnersController.update(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockNext).toHaveBeenCalled();
    });
  });

  describe('delete', () => {
    it('should delete partner successfully', async () => {
      mockDelete.mockResolvedValue(undefined);
      mockReq.params = { id: 'partner-uuid-1' };

      await partnersController.delete(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockDelete).toHaveBeenCalledWith('partner-uuid-1', tenantId, userId);
      expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({
        success: true,
      }));
    });
  });

  describe('findCustomers', () => {
    it('should return only customers', async () => {
      const mockCustomers = { data: [createMockPartner({ partnerType: 'customer' })], total: 1 };
      mockFindCustomers.mockResolvedValue(mockCustomers);
      mockReq.query = {};

      await partnersController.findCustomers(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockFindCustomers).toHaveBeenCalledWith(tenantId, expect.any(Object));
    });
  });

  describe('findSuppliers', () => {
    it('should return only suppliers', async () => {
      const mockSuppliers = { data: [createMockPartner({ partnerType: 'supplier' })], total: 1 };
      mockFindSuppliers.mockResolvedValue(mockSuppliers);
      mockReq.query = {};

      await partnersController.findSuppliers(
        mockReq as AuthenticatedRequest,
        mockRes as Response,
        mockNext
      );

      expect(mockFindSuppliers).toHaveBeenCalledWith(tenantId, expect.any(Object));
    });
  });
});
