/**
 * @alga-psa/scheduling - Schedule Entry Model Tests
 *
 * Tests for the ScheduleEntry model business logic.
 * These tests verify validation logic and error handling.
 */

import { describe, it, expect, vi } from 'vitest';
import ScheduleEntry from '@alga-psa/shared/models/scheduleEntry';

// Mock Knex to test validation logic without database
const createMockKnex = () => {
  const mockInsert = vi.fn().mockReturnThis();
  const mockWhere = vi.fn().mockReturnThis();
  const mockAndWhere = vi.fn().mockReturnThis();
  const mockWhereIn = vi.fn().mockReturnThis();
  const mockWhereNull = vi.fn().mockReturnThis();
  const mockWhereBetween = vi.fn().mockReturnThis();
  const mockOrWhereBetween = vi.fn().mockReturnThis();
  const mockFirst = vi.fn();
  const mockUpdate = vi.fn().mockReturnThis();
  const mockDel = vi.fn();
  const mockReturning = vi.fn();
  const mockSelect = vi.fn().mockReturnThis();
  const mockJoin = vi.fn().mockReturnThis();
  const mockOrderBy = vi.fn().mockReturnThis();

  const chainable = {
    insert: mockInsert,
    where: mockWhere,
    andWhere: mockAndWhere,
    whereIn: mockWhereIn,
    whereNull: mockWhereNull,
    whereBetween: mockWhereBetween,
    orWhereBetween: mockOrWhereBetween,
    first: mockFirst,
    update: mockUpdate,
    del: mockDel,
    returning: mockReturning,
    select: mockSelect,
    join: mockJoin,
    orderBy: mockOrderBy,
  };

  // Make all methods return the chainable object for chaining
  Object.keys(chainable).forEach((key) => {
    if (key !== 'first' && key !== 'del') {
      (chainable as any)[key] = vi.fn().mockReturnValue(chainable);
    }
  });

  const mockKnex = vi.fn(() => chainable);

  return {
    knex: mockKnex as any,
    mocks: chainable,
  };
};

describe('ScheduleEntry Model', () => {
  describe('getAssignedUserIds', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(
        ScheduleEntry.getAssignedUserIds(knex, '', ['entry-123'])
      ).rejects.toThrow('Tenant context is required for getting schedule entry assignees');
    });

    it('should return empty object when no entry IDs are provided', async () => {
      const { knex } = createMockKnex();

      const result = await ScheduleEntry.getAssignedUserIds(knex, 'tenant-123', []);
      expect(result).toEqual({});
    });

    it('should filter out undefined entry IDs', async () => {
      const { knex } = createMockKnex();

      const result = await ScheduleEntry.getAssignedUserIds(knex, 'tenant-123', [
        undefined,
        undefined,
      ]);
      expect(result).toEqual({});
    });
  });

  describe('updateAssignees', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(
        ScheduleEntry.updateAssignees(knex, '', 'entry-123', ['user-1'])
      ).rejects.toThrow('Tenant context is required for updating schedule entry assignees');
    });
  });

  describe('getAll', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();
      const start = new Date('2024-01-01');
      const end = new Date('2024-01-31');

      await expect(ScheduleEntry.getAll(knex, '', start, end)).rejects.toThrow(
        'Tenant context is required for getting schedule entries'
      );
    });
  });

  describe('getEarliest', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(ScheduleEntry.getEarliest(knex, '')).rejects.toThrow(
        'Tenant context is required for getting earliest schedule entry'
      );
    });
  });

  describe('get', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(ScheduleEntry.get(knex, '', 'entry-123')).rejects.toThrow(
        'Tenant context is required for getting schedule entry'
      );
    });
  });

  describe('create', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();
      const entry = {
        title: 'Test Entry',
        scheduled_start: new Date('2024-01-15T09:00:00'),
        scheduled_end: new Date('2024-01-15T10:00:00'),
        status: 'scheduled',
        work_item_id: null,
        work_item_type: 'ad_hoc' as const,
        assigned_user_ids: [],
      };
      const options = {
        assignedUserIds: ['user-1'],
      };

      await expect(ScheduleEntry.create(knex, '', entry, options)).rejects.toThrow(
        'Tenant context is required for creating schedule entry'
      );
    });
  });

  describe('update', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(ScheduleEntry.update(knex, '', 'entry-123', {})).rejects.toThrow(
        'Tenant context is required for updating schedule entry'
      );
    });
  });

  describe('delete', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(ScheduleEntry.delete(knex, '', 'entry-123')).rejects.toThrow(
        'Tenant context is required for deleting schedule entry'
      );
    });
  });

  describe('getByWorkItem', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(
        ScheduleEntry.getByWorkItem(knex, '', 'work-item-123', 'ticket')
      ).rejects.toThrow('Tenant context is required for getting schedule entries by work item');
    });
  });

  describe('getByUser', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(ScheduleEntry.getByUser(knex, '', 'user-123')).rejects.toThrow(
        'Tenant context is required for getting schedule entries by user'
      );
    });
  });

  describe('parseRecurrencePattern', () => {
    it('should return null for null input', () => {
      expect(ScheduleEntry.parseRecurrencePattern(null)).toBeNull();
    });

    it('should return the object as-is for object input', () => {
      const pattern = {
        frequency: 'daily' as const,
        interval: 1,
        startDate: new Date('2024-01-01'),
      };

      expect(ScheduleEntry.parseRecurrencePattern(pattern)).toEqual(pattern);
    });

    it('should parse valid JSON string', () => {
      const pattern = {
        frequency: 'weekly',
        interval: 2,
        startDate: '2024-01-01',
      };
      const jsonString = JSON.stringify(pattern);

      const result = ScheduleEntry.parseRecurrencePattern(jsonString);
      expect(result).toEqual(pattern);
    });

    it('should return null for invalid JSON string', () => {
      const invalidJson = 'not valid json';

      // The function should not throw, but return null
      const result = ScheduleEntry.parseRecurrencePattern(invalidJson);
      expect(result).toBeNull();
    });
  });
});
