import { queryArchive } from '../../src/libs/archive/query';

import { listArchiveFiles, openArchiveSequelize } from '../../src/libs/archive/store';
import { ArchiveMetadata } from '../../src/store/models/archive-metadata';

jest.mock('../../src/libs/archive/store', () => ({
  listArchiveFiles: jest.fn(),
  openArchiveSequelize: jest.fn(),
}));

jest.mock('../../src/store/models/archive-metadata', () => ({
  ArchiveMetadata: {
    findOne: jest.fn(),
    findAll: jest.fn(),
  },
}));

jest.mock('../../src/libs/logger', () => ({
  __esModule: true,
  default: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
}));

const mockListArchiveFiles = listArchiveFiles as jest.MockedFunction<typeof listArchiveFiles>;
const mockOpenArchiveSequelize = openArchiveSequelize as jest.MockedFunction<typeof openArchiveSequelize>;
const mockArchiveMetadataFindAll = (ArchiveMetadata as any).findAll as jest.MockedFunction<any>;

describe('archive/query', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('queryArchive', () => {
    it('should reject invalid table names', async () => {
      await expect(
        queryArchive({
          table: 'DROP TABLE users;--',
          from: Math.floor(Date.now() / 1000) - 86400,
          page: 1,
          limit: 10,
        })
      ).rejects.toThrow('Invalid table name');
    });

    it('should reject table names starting with numbers', async () => {
      await expect(
        queryArchive({
          table: '123_table',
          from: Math.floor(Date.now() / 1000) - 86400,
          page: 1,
          limit: 10,
        })
      ).rejects.toThrow('Invalid table name');
    });

    it('should accept valid table names', async () => {
      mockListArchiveFiles.mockReturnValue([]);

      const result = await queryArchive({
        table: 'meter_events',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 1,
        limit: 10,
      });

      expect(result).toEqual({ data: [], total: 0, archiveFiles: [] });
    });

    it('should return empty result when no archive files exist', async () => {
      mockListArchiveFiles.mockReturnValue([]);

      const result = await queryArchive({
        table: 'invoices',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 1,
        limit: 10,
      });

      expect(result.data).toHaveLength(0);
      expect(result.total).toBe(0);
      expect(result.archiveFiles).toHaveLength(0);
    });

    it('should query archive files and return paginated results', async () => {
      const mockClose = jest.fn();
      // Mock query: first call checks table existence, second call fetches data
      const mockQuery = jest.fn().mockImplementation((sql: string) => {
        if (sql.includes('sqlite_master')) {
          return [[{ name: 'invoices' }]]; // Table exists
        }
        return [
          { id: '1', created_at: new Date('2024-01-15') },
          { id: '2', created_at: new Date('2024-01-14') },
        ];
      });

      mockListArchiveFiles.mockReturnValue(['/tmp/archive-2024.db']);
      mockOpenArchiveSequelize.mockReturnValue({
        query: mockQuery,
        close: mockClose,
      } as any);
      mockArchiveMetadataFindAll.mockResolvedValue([
        {
          id: 'meta-1',
          query_count: 0,
          query_actor_ids: [],
          update: jest.fn(),
        },
      ]);

      const result = await queryArchive({
        table: 'invoices',
        from: Math.floor(new Date('2024-01-01').getTime() / 1000),
        to: Math.floor(new Date('2024-01-31').getTime() / 1000),
        page: 1,
        limit: 10,
      });

      expect(result.data).toHaveLength(2);
      expect(result.total).toBe(2);
      expect(mockClose).toHaveBeenCalled();
    });

    it('should skip archive files that do not have the requested table', async () => {
      const mockClose = jest.fn();
      // Return empty array for sqlite_master query (table doesn't exist)
      const mockQuery = jest.fn().mockResolvedValue([[]]);

      mockListArchiveFiles.mockReturnValue(['/tmp/archive-2024.db']);
      mockOpenArchiveSequelize.mockReturnValue({
        query: mockQuery,
        close: mockClose,
      } as any);

      const result = await queryArchive({
        table: 'nonexistent_table',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 1,
        limit: 10,
      });

      expect(result.data).toHaveLength(0);
      expect(mockClose).toHaveBeenCalled();
    });

    it('should handle query errors gracefully', async () => {
      const mockClose = jest.fn();
      const mockQuery = jest.fn().mockImplementation((sql: string) => {
        if (sql.includes('sqlite_master')) {
          return [[{ name: 'invoices' }]]; // Table exists
        }
        throw new Error('Query failed');
      });

      mockListArchiveFiles.mockReturnValue(['/tmp/archive-2024.db']);
      mockOpenArchiveSequelize.mockReturnValue({
        query: mockQuery,
        close: mockClose,
      } as any);

      const result = await queryArchive({
        table: 'invoices',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 1,
        limit: 10,
      });

      expect(result.data).toHaveLength(0);
      expect(mockClose).toHaveBeenCalled();
    });

    it('should apply customer_id filter correctly for regular tables', async () => {
      const mockClose = jest.fn();
      const mockQuery = jest.fn().mockImplementation((sql: string) => {
        if (sql.includes('sqlite_master')) {
          return [[{ name: 'invoices' }]];
        }
        return [];
      });

      mockListArchiveFiles.mockReturnValue(['/tmp/archive-2024.db']);
      mockOpenArchiveSequelize.mockReturnValue({
        query: mockQuery,
        close: mockClose,
      } as any);

      await queryArchive({
        table: 'invoices',
        customer_id: 'cus_123',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 1,
        limit: 10,
      });

      expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('customer_id = :customerId'), expect.any(Object));
    });

    it('should apply customer_id filter with json_extract for meter_events', async () => {
      const mockClose = jest.fn();
      const mockQuery = jest.fn().mockImplementation((sql: string) => {
        if (sql.includes('sqlite_master')) {
          return [[{ name: 'meter_events' }]];
        }
        return [];
      });

      mockListArchiveFiles.mockReturnValue(['/tmp/archive-2024.db']);
      mockOpenArchiveSequelize.mockReturnValue({
        query: mockQuery,
        close: mockClose,
      } as any);

      await queryArchive({
        table: 'meter_events',
        customer_id: 'cus_123',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 1,
        limit: 10,
      });

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining("json_extract(payload, '$.customer_id')"),
        expect.any(Object)
      );
    });

    it('should paginate results correctly', async () => {
      const mockClose = jest.fn();
      const mockQuery = jest.fn().mockImplementation((sql: string) => {
        if (sql.includes('sqlite_master')) {
          return [[{ name: 'invoices' }]];
        }
        return Array.from({ length: 25 }, (_, i) => ({
          id: String(i + 1),
          created_at: new Date(Date.now() - i * 1000),
        }));
      });

      mockListArchiveFiles.mockReturnValue(['/tmp/archive-2024.db']);
      mockOpenArchiveSequelize.mockReturnValue({
        query: mockQuery,
        close: mockClose,
      } as any);
      mockArchiveMetadataFindAll.mockResolvedValue([
        {
          id: 'meta-1',
          query_count: 0,
          query_actor_ids: [],
          update: jest.fn(),
        },
      ]);

      const result = await queryArchive({
        table: 'invoices',
        from: Math.floor(Date.now() / 1000) - 86400,
        page: 2,
        limit: 10,
      });

      expect(result.data).toHaveLength(10);
      expect(result.total).toBe(25);
    });
  });
});
