import { describe, it, expect, vi, afterEach } from 'vitest';
import {
  isCompatibleVersion,
  getVersionMismatchError,
  KRAIKEN_LIB_VERSION,
  COMPATIBLE_CONTRACT_VERSIONS,
} from 'kraiken-lib/version';

// ---------------------------------------------------------------------------
// isCompatibleVersion (from kraiken-lib/version, aliased to source)
// ---------------------------------------------------------------------------

describe('isCompatibleVersion', () => {
  it('returns true for each version in COMPATIBLE_CONTRACT_VERSIONS', () => {
    for (const v of COMPATIBLE_CONTRACT_VERSIONS) {
      expect(isCompatibleVersion(v)).toBe(true);
    }
  });

  it('returns false for version 0', () => {
    expect(isCompatibleVersion(0)).toBe(false);
  });

  it('returns false for a future version not yet listed', () => {
    expect(isCompatibleVersion(9999)).toBe(false);
  });

  it('returns false for negative version', () => {
    expect(isCompatibleVersion(-1)).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// getVersionMismatchError (from kraiken-lib/version)
// ---------------------------------------------------------------------------

describe('getVersionMismatchError', () => {
  it('includes the bad contract version in the output (ponder context)', () => {
    const msg = getVersionMismatchError(99, 'ponder');
    expect(msg).toContain('99');
  });

  it('includes the library version in the output', () => {
    const msg = getVersionMismatchError(99, 'ponder');
    expect(msg).toContain(String(KRAIKEN_LIB_VERSION));
  });

  it('includes ponder-specific remediation steps', () => {
    const msg = getVersionMismatchError(99, 'ponder');
    expect(msg).toContain('COMPATIBLE_CONTRACT_VERSIONS');
  });

  it('includes frontend-specific remediation steps', () => {
    const msg = getVersionMismatchError(99, 'frontend');
    expect(msg).toContain('administrator');
  });

  it('formats compatible versions list', () => {
    const msg = getVersionMismatchError(99, 'ponder');
    for (const v of COMPATIBLE_CONTRACT_VERSIONS) {
      expect(msg).toContain(String(v));
    }
  });

  it('returns a multi-line string (box drawing)', () => {
    const msg = getVersionMismatchError(99, 'ponder');
    expect(msg.split('\n').length).toBeGreaterThan(3);
  });
});

// ---------------------------------------------------------------------------
// validateContractVersion (helpers/version.ts) — mock the Ponder context
// ---------------------------------------------------------------------------

describe('validateContractVersion', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('logs success and upserts metadata on compatible version', async () => {
    const { validateContractVersion } = await import('../src/helpers/version.js');

    const setFn = vi.fn().mockResolvedValue(undefined);
    const ctx = {
      client: {
        readContract: vi.fn().mockResolvedValue(KRAIKEN_LIB_VERSION),
      },
      contracts: {
        Kraiken: { abi: [], address: '0x0001' as `0x${string}` },
      },
      db: {
        find: vi.fn().mockResolvedValue(null),
        insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }),
        update: vi.fn().mockReturnValue({ set: setFn }),
      },
      logger: {
        info: vi.fn(),
        error: vi.fn(),
        warn: vi.fn(),
      },
    };

    const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never);

    await validateContractVersion(ctx as unknown as import('ponder:registry').Context);

    expect(exitSpy).not.toHaveBeenCalled();
    expect(ctx.logger.info).toHaveBeenCalled();
  });

  it('calls process.exit on incompatible contract version', async () => {
    const { validateContractVersion } = await import('../src/helpers/version.js');

    const ctx = {
      client: {
        readContract: vi.fn().mockResolvedValue(9999n),
      },
      contracts: {
        Kraiken: { abi: [], address: '0x0001' as `0x${string}` },
      },
      db: {
        find: vi.fn().mockResolvedValue(null),
        insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }),
        update: vi.fn().mockReturnValue({ set: vi.fn().mockResolvedValue(undefined) }),
      },
      logger: {
        info: vi.fn(),
        error: vi.fn(),
        warn: vi.fn(),
      },
    };

    const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never);

    await validateContractVersion(ctx as unknown as import('ponder:registry').Context);

    expect(exitSpy).toHaveBeenCalledWith(1);
  });

  it('calls process.exit when readContract throws', async () => {
    const { validateContractVersion } = await import('../src/helpers/version.js');

    const ctx = {
      client: {
        readContract: vi.fn().mockRejectedValue(new Error('network error')),
      },
      contracts: {
        Kraiken: { abi: [], address: '0x0001' as `0x${string}` },
      },
      db: {
        find: vi.fn().mockResolvedValue(null),
        insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }),
        update: vi.fn().mockReturnValue({ set: vi.fn().mockResolvedValue(undefined) }),
      },
      logger: {
        info: vi.fn(),
        error: vi.fn(),
        warn: vi.fn(),
      },
    };

    const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never);

    await validateContractVersion(ctx as unknown as import('ponder:registry').Context);

    expect(exitSpy).toHaveBeenCalledWith(1);
  });

  it('updates existing metadata when a row already exists', async () => {
    const { validateContractVersion } = await import('../src/helpers/version.js');

    const setFn = vi.fn().mockResolvedValue(undefined);
    const existingMeta = { id: 'stack-meta', contractVersion: 1 };
    const ctx = {
      client: {
        readContract: vi.fn().mockResolvedValue(KRAIKEN_LIB_VERSION),
      },
      contracts: {
        Kraiken: { abi: [], address: '0x0001' as `0x${string}` },
      },
      db: {
        find: vi.fn().mockResolvedValue(existingMeta),
        insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }),
        update: vi.fn().mockReturnValue({ set: setFn }),
      },
      logger: {
        info: vi.fn(),
        error: vi.fn(),
        warn: vi.fn(),
      },
    };

    const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as () => never);

    await validateContractVersion(ctx as unknown as import('ponder:registry').Context);

    expect(exitSpy).not.toHaveBeenCalled();
    expect(ctx.db.update).toHaveBeenCalled();
    expect(ctx.db.insert).not.toHaveBeenCalled();
  });
});
