import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ReportService } from './report.service';

const mocks = vi.hoisted(() => {
  const mockCreate = vi.fn();
  const mockUpdate = vi.fn();
  const mockFindUniqueOrThrow = vi.fn();
  const mockFindMany = vi.fn();
  const mockFindFirst = vi.fn();
  const mockSubscriptionFindMany = vi.fn();
  const mockPrisma = {
    securityReport: {
      create: mockCreate,
      update: mockUpdate,
      findUniqueOrThrow: mockFindUniqueOrThrow,
      findMany: mockFindMany,
      findFirst: mockFindFirst,
    },
    subscription: {
      findMany: mockSubscriptionFindMany,
    },
  };
  return {
    mockCreate,
    mockUpdate,
    mockFindUniqueOrThrow,
    mockFindMany,
    mockFindFirst,
    mockSubscriptionFindMany,
    mockPrisma,
  };
});

const {
  mockCreate,
  mockUpdate,
  mockFindUniqueOrThrow,
  mockFindMany,
  mockFindFirst,
  mockSubscriptionFindMany,
  mockPrisma,
} = mocks;

vi.mock('@shieldai/db', () => ({
  prisma: mocks.mockPrisma,
}));

vi.mock('fs', () => ({
  default: {
    existsSync: vi.fn(() => false),
    mkdirSync: vi.fn(),
    writeFileSync: vi.fn(),
  },
  existsSync: vi.fn(() => false),
  mkdirSync: vi.fn(),
  writeFileSync: vi.fn(),
}));

vi.mock('./data-collector', () => ({
  collectAllReportData: vi.fn(() =>
    Promise.resolve({
      exposureSummary: {
        totalExposures: 5, newExposures: 2, resolvedExposures: 1,
        criticalExposures: 1, warningExposures: 2, infoExposures: 2,
        exposuresBySource: {},
      },
      spamStats: {
        callsBlocked: 10, textsBlocked: 5, callsFlagged: 2, textsFlagged: 1,
        falsePositives: 0, totalSpamEvents: 15,
      },
      voiceStats: {
        analysesRun: 20, threatsDetected: 1, enrollmentsActive: 1,
        syntheticDetections: 1, voiceMismatchEvents: 1,
      },
      recommendations: [],
      protectionScore: 80,
    })
  ),
}));

vi.mock('./html-renderer', () => ({
  htmlRenderer: {
    render: vi.fn(() => '<html>Mock HTML</html>'),
  },
}));

vi.mock('./pdf-generator', () => ({
  pdfGenerator: {
    generate: vi.fn(() => Promise.resolve(Buffer.from('mock-pdf'))),
  },
}));

describe('ReportService', () => {
  let service: ReportService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new ReportService();
  });

  describe('generateReport', () => {
    it('creates and completes a report successfully', async () => {
      const reportId = 'report-1';
      mockCreate.mockResolvedValueOnce({
        id: reportId,
        userId: 'user-1',
        subscriptionId: 'sub-1',
        reportType: 'MONTHLY_PLUS',
        status: 'GENERATING',
        periodStart: new Date('2025-01-01'),
        periodEnd: new Date('2025-01-31'),
        title: 'Monthly Protection Report — January 2025',
      });
      mockUpdate.mockResolvedValueOnce({
        id: reportId,
        userId: 'user-1',
        reportType: 'MONTHLY_PLUS',
        status: 'COMPLETED',
        periodStart: new Date('2025-01-01'),
        periodEnd: new Date('2025-01-31'),
        title: 'Monthly Protection Report — January 2025',
        summary: 'Protection Score: 80/100. 15 spam event(s) blocked.',
        htmlContent: '<html>Mock HTML</html>',
        pdfUrl: 'https://app.shieldai.com/api/v1/reports/report-1/pdf',
        dataPayload: '{}',
        error: null,
        createdAt: new Date(),
        deliveredAt: null,
      });

      const result = await service.generateReport({
        userId: 'user-1',
        subscriptionId: 'sub-1',
        reportType: 'MONTHLY_PLUS',
        periodStart: new Date('2025-01-01'),
        periodEnd: new Date('2025-01-31'),
      });

      expect(mockCreate).toHaveBeenCalledTimes(1);
      expect(mockUpdate).toHaveBeenCalledTimes(1);
      expect(result.status).toBe('COMPLETED');
      expect(result.reportType).toBe('MONTHLY_PLUS');
    });

    it('sets status to FAILED on error', async () => {
      const reportId = 'report-2';
      mockCreate.mockResolvedValueOnce({
        id: reportId,
        userId: 'user-1',
        subscriptionId: 'sub-1',
        reportType: 'MONTHLY_PLUS',
        status: 'GENERATING',
        periodStart: new Date('2025-01-01'),
        periodEnd: new Date('2025-01-31'),
        title: 'Monthly Protection Report',
      });
      mockUpdate.mockResolvedValueOnce({
        id: reportId,
        status: 'FAILED',
        error: 'Data collection failed',
      });
      mockFindUniqueOrThrow.mockResolvedValueOnce({
        id: reportId,
        userId: 'user-1',
        reportType: 'MONTHLY_PLUS',
        status: 'FAILED',
        periodStart: new Date('2025-01-01'),
        periodEnd: new Date('2025-01-31'),
        title: 'Monthly Protection Report',
        summary: null,
        pdfUrl: null,
        dataPayload: null,
        error: 'Data collection failed',
        createdAt: new Date(),
        deliveredAt: null,
      });

      // Force data collector to throw
      const dc = await import('./data-collector');
      vi.mocked(dc.collectAllReportData).mockResolvedValueOnce({
        exposureSummary: {
          totalExposures: 0, newExposures: 0, resolvedExposures: 0,
          criticalExposures: 0, warningExposures: 0, infoExposures: 0,
          exposuresBySource: {},
        },
        spamStats: {
          callsBlocked: 0, textsBlocked: 0, callsFlagged: 0, textsFlagged: 0,
          falsePositives: 0, totalSpamEvents: 50,
        },
        voiceStats: {
          analysesRun: 0, threatsDetected: 0, enrollmentsActive: 0,
          syntheticDetections: 0, voiceMismatchEvents: 0,
        },
        recommendations: [],
        protectionScore: 85,
      });

      const result = await service.generateReport({
        userId: 'user-1',
        subscriptionId: 'sub-1',
        reportType: 'MONTHLY_PLUS',
      });

      expect(result.status).toBe('FAILED');
    });
  });

  describe('getReportHistory', () => {
    it('returns paginated report history', async () => {
      mockFindMany.mockResolvedValue([
        {
          id: 'r1', userId: 'user-1', reportType: 'MONTHLY_PLUS', status: 'COMPLETED',
          periodStart: new Date('2025-01-01'), periodEnd: new Date('2025-01-31'),
          title: 'Jan Report', summary: 'Good', pdfUrl: '/pdf/1',
          dataPayload: '{}', error: null, createdAt: new Date(), deliveredAt: null,
        },
        {
          id: 'r2', userId: 'user-1', reportType: 'MONTHLY_PLUS', status: 'COMPLETED',
          periodStart: new Date('2024-12-01'), periodEnd: new Date('2024-12-31'),
          title: 'Dec Report', summary: 'Good', pdfUrl: '/pdf/2',
          dataPayload: '{}', error: null, createdAt: new Date(), deliveredAt: null,
        },
      ]);

      const result = await service.getReportHistory('user-1', 10, 0);

      expect(result).toHaveLength(2);
      expect(mockFindMany).toHaveBeenCalledWith({
        where: { userId: 'user-1' },
        orderBy: { createdAt: 'desc' },
        take: 10,
        skip: 0,
      });
    });
  });

  describe('getReportById', () => {
    it('returns report when found', async () => {
      mockFindFirst.mockResolvedValue({
        id: 'r1', userId: 'user-1', reportType: 'MONTHLY_PLUS', status: 'COMPLETED',
        periodStart: new Date(), periodEnd: new Date(),
        title: 'Test Report', summary: 'ok', pdfUrl: '/pdf',
        dataPayload: '{}', error: null, createdAt: new Date(), deliveredAt: null,
      });

      const result = await service.getReportById('user-1', 'r1');
      expect(result.id).toBe('r1');
    });

    it('throws when report not found', async () => {
      mockFindFirst.mockResolvedValue(null);

      await expect(service.getReportById('user-1', 'r99')).rejects.toThrow(
        'Report r99 not found'
      );
    });
  });

  describe('scheduleMonthlyReports', () => {
    it('creates monthly reports for Plus subscriptions', async () => {
      mockSubscriptionFindMany.mockResolvedValue([
        { id: 'sub-1', userId: 'user-1', user: { email: 'u1@test.com' } },
        { id: 'sub-2', userId: 'user-2', user: { email: 'u2@test.com' } },
      ]);
      mockFindFirst.mockResolvedValue(null);
      mockCreate.mockResolvedValue({ id: 'new-report-1' });

      const result = await service.scheduleMonthlyReports();

      expect(result.length).toBeGreaterThan(0);
      expect(mockCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            reportType: 'MONTHLY_PLUS',
            status: 'PENDING',
          }),
        })
      );
    });

    it('skips subscriptions that already have a report for the period', async () => {
      mockSubscriptionFindMany.mockResolvedValue([
        { id: 'sub-1', userId: 'user-1', user: { email: 'u1@test.com' } },
      ]);
      mockFindFirst.mockResolvedValue({ id: 'existing' });

      const result = await service.scheduleMonthlyReports();

      expect(result).toHaveLength(0);
    });
  });

  describe('scheduleAnnualReports', () => {
    it('creates annual reports for Premium subscriptions due', async () => {
      const now = new Date();
      mockSubscriptionFindMany.mockResolvedValue([
        {
          id: 'sub-1',
          userId: 'user-1',
          currentPeriodStart: new Date(now.getFullYear() - 1, now.getMonth(), now.getDate()),
        },
      ]);
      mockFindFirst.mockResolvedValue(null);
      mockCreate.mockResolvedValue({ id: 'annual-report-1' });

      const result = await service.scheduleAnnualReports();

      expect(result.length).toBeGreaterThan(0);
      expect(mockCreate).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            reportType: 'ANNUAL_PREMIUM',
            status: 'PENDING',
          }),
        })
      );
    });
  });
});
