import { acquireArchiveLock, releaseArchiveLock } from '../../src/libs/archive/lock';
import { ArchiveLock } from '../../src/store/models/archive-lock';

jest.mock('../../src/store/models/archive-lock', () => {
  const mockArchiveLock = {
    sequelize: {
      transaction: jest.fn(),
    },
    findByPk: jest.fn(),
    create: jest.fn(),
    update: jest.fn(),
  };
  return { ArchiveLock: mockArchiveLock };
});

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

  describe('acquireArchiveLock', () => {
    it('should return false when sequelize is not available', async () => {
      (ArchiveLock as any).sequelize = null;

      const result = await acquireArchiveLock('instance-1');

      expect(result).toBe(false);
    });

    it('should create new lock when no existing lock', async () => {
      const mockTransaction = jest.fn();
      (ArchiveLock as any).sequelize = {
        transaction: jest.fn((callback: any) => callback(mockTransaction)),
      };
      (ArchiveLock.findByPk as jest.Mock).mockResolvedValue(null);
      (ArchiveLock.create as jest.Mock).mockResolvedValue({});

      const result = await acquireArchiveLock('instance-1');

      expect(result).toBe(true);
      expect(ArchiveLock.create).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 'archive_job',
          locked_by: 'instance-1',
        }),
        expect.any(Object)
      );
    });

    it('should acquire expired lock', async () => {
      const mockTransaction = jest.fn();
      const expiredLock = {
        expires_at: Date.now() - 1000,
        update: jest.fn().mockResolvedValue({}),
      };
      (ArchiveLock as any).sequelize = {
        transaction: jest.fn((callback: any) => callback(mockTransaction)),
      };
      (ArchiveLock.findByPk as jest.Mock).mockResolvedValue(expiredLock);

      const result = await acquireArchiveLock('instance-2');

      expect(result).toBe(true);
      expect(expiredLock.update).toHaveBeenCalledWith(
        expect.objectContaining({
          locked_by: 'instance-2',
        }),
        expect.any(Object)
      );
    });

    it('should not acquire lock when held by another instance', async () => {
      const mockTransaction = jest.fn();
      const activeLock = {
        expires_at: Date.now() + 60000,
        locked_by: 'other-instance',
      };
      (ArchiveLock as any).sequelize = {
        transaction: jest.fn((callback: any) => callback(mockTransaction)),
      };
      (ArchiveLock.findByPk as jest.Mock).mockResolvedValue(activeLock);

      const result = await acquireArchiveLock('instance-1');

      expect(result).toBe(false);
    });

    it('should acquire lock when expires_at is null (stale lock)', async () => {
      const mockTransaction = jest.fn();
      const staleLock = {
        expires_at: null,
        update: jest.fn().mockResolvedValue({}),
      };
      (ArchiveLock as any).sequelize = {
        transaction: jest.fn((callback: any) => callback(mockTransaction)),
      };
      (ArchiveLock.findByPk as jest.Mock).mockResolvedValue(staleLock);

      const result = await acquireArchiveLock('instance-1');

      expect(result).toBe(true);
      expect(staleLock.update).toHaveBeenCalled();
    });
  });

  describe('releaseArchiveLock', () => {
    it('should release lock for the given instance', async () => {
      (ArchiveLock.update as jest.Mock).mockResolvedValue([1]);
      (ArchiveLock as any).sequelize = { transaction: jest.fn() };

      await releaseArchiveLock('instance-1');

      expect(ArchiveLock.update).toHaveBeenCalledWith(
        { locked_by: null, locked_at: null, expires_at: null },
        { where: { id: 'archive_job', locked_by: 'instance-1' } }
      );
    });

    it('should not release lock held by different instance', async () => {
      (ArchiveLock.update as jest.Mock).mockResolvedValue([0]);
      (ArchiveLock as any).sequelize = { transaction: jest.fn() };

      await releaseArchiveLock('wrong-instance');

      expect(ArchiveLock.update).toHaveBeenCalledWith(expect.anything(), {
        where: { id: 'archive_job', locked_by: 'wrong-instance' },
      });
    });
  });
});
