import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { DataSource, Repository } from 'typeorm';
import { PrintTaskService } from '../../src/services/print-task.service';
import { FeiePrintTaskMt } from '../../src/entities/feie-print-task.mt.entity';
import { FeiePrinterMt } from '../../src/entities/feie-printer.mt.entity';
import type { FeieApiConfig, CreatePrintTaskDto } from '../../src/types/feie.types';

// Mock dependencies
vi.mock('../../src/services/feie-api.service', () => {
  return {
    FeieApiService: vi.fn().mockImplementation(() => ({
      printReceipt: vi.fn().mockResolvedValue({ ret: 0, msg: 'success', data: '123456' }),
      queryOrderStatus: vi.fn().mockResolvedValue({ ret: 0, data: '已打印' })
    }))
  };
});

vi.mock('../../src/services/printer.service', () => {
  return {
    PrinterService: vi.fn().mockImplementation(() => ({}))
  };
});

vi.mock('@d8d/shared-crud', () => {
  return {
    GenericCrudService: vi.fn().mockImplementation(() => ({
      create: vi.fn(),
      findOne: vi.fn(),
      findMany: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      repository: {
        findAndCount: vi.fn()
      }
    }))
  };
});

describe('PrintTaskService', () => {
  let printTaskService: PrintTaskService;
  let mockDataSource: DataSource;
  let mockPrinterRepository: Repository<FeiePrinterMt>;
  const mockFeieConfig: FeieApiConfig = {
    baseUrl: 'http://api.feieyun.cn/Api/Open/',
    user: 'test_user',
    ukey: 'test_ukey'
  };

  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();

    mockPrinterRepository = {
      findOne: vi.fn()
    } as unknown as Repository<FeiePrinterMt>;

    mockDataSource = {
      getRepository: vi.fn().mockReturnValue(mockPrinterRepository)
    } as unknown as DataSource;

    printTaskService = new PrintTaskService(mockDataSource, mockFeieConfig);
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('createPrintTask', () => {
    it('应该成功创建立即打印任务', async () => {
      const tenantId = 1;
      const taskDto: CreatePrintTaskDto = {
        printerSn: 'TEST123456',
        content: '测试打印内容',
        printType: 'RECEIPT',
        delaySeconds: 0
      };

      const mockPrinter: Partial<FeiePrinterMt> = {
        id: 1,
        tenantId,
        printerSn: taskDto.printerSn,
        printerStatus: 'ACTIVE'
      };

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId: 'FEIE_123456789_1234',
        printerSn: taskDto.printerSn,
        content: taskDto.content,
        printType: taskDto.printType,
        printStatus: 'PENDING'
      };

      // Mock dependencies
      vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(mockPrinter as FeiePrinterMt);
      (printTaskService as any).create = vi.fn().mockResolvedValue(mockTask);
      (printTaskService as any).executePrintTask = vi.fn().mockResolvedValue(mockTask);

      const result = await printTaskService.createPrintTask(tenantId, taskDto);

      expect(result).toEqual(mockTask);
      expect(mockPrinterRepository.findOne).toHaveBeenCalledWith({
        where: { tenantId, printerSn: taskDto.printerSn }
      });
      expect((printTaskService as any).executePrintTask).toHaveBeenCalledWith(tenantId, mockTask.taskId);
    });

    it('应该成功创建延迟打印任务', async () => {
      const tenantId = 1;
      const taskDto: CreatePrintTaskDto = {
        printerSn: 'TEST123456',
        content: '测试打印内容',
        printType: 'RECEIPT',
        delaySeconds: 120 // 2分钟延迟
      };

      const mockPrinter: Partial<FeiePrinterMt> = {
        id: 1,
        tenantId,
        printerSn: taskDto.printerSn,
        printerStatus: 'ACTIVE'
      };

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId: 'FEIE_123456789_1234',
        printerSn: taskDto.printerSn,
        content: taskDto.content,
        printType: taskDto.printType,
        printStatus: 'DELAYED',
        scheduledAt: new Date(Date.now() + 120000)
      };

      // Mock dependencies
      vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(mockPrinter as FeiePrinterMt);
      (printTaskService as any).create = vi.fn().mockResolvedValue(mockTask);

      const result = await printTaskService.createPrintTask(tenantId, taskDto);

      expect(result).toEqual(mockTask);
      expect(result.printStatus).toBe('DELAYED');
      expect(result.scheduledAt).toBeInstanceOf(Date);
      expect((printTaskService as any).executePrintTask).not.toHaveBeenCalled();
    });

    it('应该在打印机不存在时抛出错误', async () => {
      const tenantId = 1;
      const taskDto: CreatePrintTaskDto = {
        printerSn: 'NONEXISTENT',
        content: '测试打印内容',
        printType: 'RECEIPT'
      };

      // Mock printer not found
      vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(null);

      await expect(printTaskService.createPrintTask(tenantId, taskDto))
        .rejects
        .toThrow('打印机不存在');
    });
  });

  describe('executePrintTask', () => {
    it('应该成功执行打印任务', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printerSn: 'TEST123456',
        content: '测试内容',
        printType: 'RECEIPT',
        printStatus: 'PENDING',
        retryCount: 0,
        maxRetries: 3
      };

      const updatedTask: Partial<FeiePrintTaskMt> = {
        ...mockTask,
        printStatus: 'SUCCESS',
        printedAt: new Date()
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
      (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);

      const result = await printTaskService.executePrintTask(tenantId, taskId);

      expect(result).toEqual(updatedTask);
      expect((printTaskService as any).update).toHaveBeenCalledWith(mockTask.id, {
        printStatus: 'PRINTING',
        errorMessage: null
      });
    });

    it('应该在任务已取消时抛出错误', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printStatus: 'CANCELLED'
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);

      await expect(printTaskService.executePrintTask(tenantId, taskId))
        .rejects
        .toThrow('打印任务已取消');
    });

    it('应该在任务已完成时抛出错误', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printStatus: 'SUCCESS'
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);

      await expect(printTaskService.executePrintTask(tenantId, taskId))
        .rejects
        .toThrow('打印任务已完成');
    });

    it('应该在打印失败时重试', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printerSn: 'TEST123456',
        content: '测试内容',
        printType: 'RECEIPT',
        printStatus: 'PENDING',
        retryCount: 0,
        maxRetries: 3
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
      (printTaskService as any).update = vi.fn().mockImplementation(async (id, data) => ({
        ...mockTask,
        ...data
      }));

      // Mock printReceipt to throw error
      const mockFeieApiService = {
        printReceipt: vi.fn().mockRejectedValue(new Error('打印失败'))
      };
      (printTaskService as any).feieApiService = mockFeieApiService;

      await expect(printTaskService.executePrintTask(tenantId, taskId))
        .rejects
        .toThrow('打印失败');

      // Check that retry was scheduled
      expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 5000);
    });
  });

  describe('cancelPrintTask', () => {
    it('应该成功取消打印任务', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printStatus: 'PENDING'
      };

      const updatedTask: Partial<FeiePrintTaskMt> = {
        ...mockTask,
        printStatus: 'CANCELLED',
        cancelledAt: new Date(),
        cancelReason: 'MANUAL'
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
      (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);

      const result = await printTaskService.cancelPrintTask(tenantId, taskId);

      expect(result).toEqual(updatedTask);
    });

    it('应该在任务已完成时抛出错误', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printStatus: 'SUCCESS'
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);

      await expect(printTaskService.cancelPrintTask(tenantId, taskId))
        .rejects
        .toThrow('打印任务状态为SUCCESS，无法取消');
    });
  });

  describe('retryPrintTask', () => {
    it('应该成功重试失败的打印任务', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printStatus: 'FAILED',
        errorMessage: '之前的错误'
      };

      const updatedTask: Partial<FeiePrintTaskMt> = {
        ...mockTask,
        printStatus: 'PENDING',
        errorMessage: null,
        retryCount: 0
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
      (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);
      (printTaskService as any).executePrintTask = vi.fn().mockResolvedValue(updatedTask);

      const result = await printTaskService.retryPrintTask(tenantId, taskId);

      expect(result).toEqual(updatedTask);
      expect((printTaskService as any).executePrintTask).toHaveBeenCalledWith(tenantId, taskId);
    });

    it('应该在任务不是失败状态时抛出错误', async () => {
      const tenantId = 1;
      const taskId = 'FEIE_123456789_1234';

      const mockTask: Partial<FeiePrintTaskMt> = {
        id: 1,
        tenantId,
        taskId,
        printStatus: 'PENDING'
      };

      // Mock dependencies
      (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);

      await expect(printTaskService.retryPrintTask(tenantId, taskId))
        .rejects
        .toThrow('只有失败的打印任务可以重试');
    });
  });
});