import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ref, nextTick, type Ref } from 'vue';
import { useSnatchSelection } from '../useSnatchSelection';
import { usePositions, type Position } from '../usePositions';
import { useStake } from '../useStake';
import { useWallet } from '../useWallet';
import { useStatCollection } from '../useStatCollection';
import { useAdjustTaxRate } from '../useAdjustTaxRates';

interface MockPositionsReturn {
  activePositions: Ref<Partial<Position>[]>;
}

interface MockStakeReturn {
  stakingAmountShares: bigint;
  taxRateIndex: number;
}

interface MockWalletReturn {
  account: { address: string };
}

interface MockStatCollectionReturn {
  stakeTotalSupply?: bigint;
  kraikenTotalSupply?: bigint;
  outstandingStake?: bigint;
}

interface MockAdjustTaxRateReturn {
  taxRates: Array<{ index: number; year: number; daily: number; decimal: number }>;
}

// Mock all composables
vi.mock('../usePositions', () => ({
  usePositions: vi.fn(),
}));

vi.mock('../useStake', () => ({
  useStake: vi.fn(),
}));

vi.mock('../useWallet', () => ({
  useWallet: vi.fn(),
}));

vi.mock('../useStatCollection', () => ({
  useStatCollection: vi.fn(),
}));

vi.mock('../useAdjustTaxRates', () => ({
  useAdjustTaxRate: vi.fn(),
}));

vi.mock('kraiken-lib/staking', () => ({
  calculateSnatchShortfall: vi.fn((outstandingStake, stakingShares, _stakeTotalSupply) => {
    return stakingShares > outstandingStake ? 0n : outstandingStake - stakingShares;
  }),
}));

vi.mock('kraiken-lib/snatch', () => ({
  selectSnatchPositions: vi.fn((candidates, options) => {
    if (candidates.length === 0) {
      return { selected: [], remainingShortfall: options.shortfallShares };
    }
    return {
      selected: candidates,
      remainingShortfall: 0n,
      maxSelectedTaxRateIndex: candidates[candidates.length - 1].taxRateIndex,
    };
  }),
  minimumTaxRate: vi.fn(() => 0.01),
}));

describe('useSnatchSelection', () => {
  beforeEach(() => {
    // Reset all mocks
    vi.clearAllMocks();

    // Setup default mock values with proper refs
    vi.mocked(usePositions).mockReturnValue({
      activePositions: ref([]),
    } as MockPositionsReturn);

    vi.mocked(useStake).mockReturnValue({
      stakingAmountShares: 0n,
      taxRateIndex: 0,
    } as MockStakeReturn);

    vi.mocked(useWallet).mockReturnValue({
      account: { address: '0x123' },
    } as MockWalletReturn);

    // Mock with realistic values for local computation
    // stakeTotalSupply is typically 10^(18+7) = 10^25 from contract
    // kraikenTotalSupply is dynamic but starts around 10^25
    vi.mocked(useStatCollection).mockReturnValue({
      stakeTotalSupply: 10000000000000000000000000n, // 10^25
      kraikenTotalSupply: 10000000000000000000000000n, // 10^25
      outstandingStake: 500n,
    } as MockStatCollectionReturn);

    vi.mocked(useAdjustTaxRate).mockReturnValue({
      taxRates: [
        { index: 0, year: 1, daily: 0.00274, decimal: 0.01 },
        { index: 1, year: 2, daily: 0.00548, decimal: 0.02 },
        { index: 2, year: 3, daily: 0.00822, decimal: 0.03 },
      ],
    } as MockAdjustTaxRateReturn);
  });

  it('should initialize with empty snatchable positions', () => {
    const { snatchablePositions } = useSnatchSelection();
    expect(snatchablePositions.value).toEqual([]);
  });

  it('should handle no active positions', () => {
    const { snatchablePositions, floorTax } = useSnatchSelection();
    expect(snatchablePositions.value).toEqual([]);
    expect(floorTax.value).toBe(1);
  });

  it('should handle no shortfall', () => {
    vi.mocked(useStatCollection).mockReturnValue({
      stakeTotalSupply: 1000n,
      outstandingStake: 100n,
    });

    vi.mocked(useStake).mockReturnValue({
      stakingAmountShares: 900n,
      taxRateIndex: 0,
    });

    const { snatchablePositions, openPositionsAvailable } = useSnatchSelection();
    expect(snatchablePositions.value).toEqual([]);
    expect(openPositionsAvailable.value).toBe(true);
  });

  it('should filter out positions with higher or equal tax rate index', async () => {
    vi.mocked(usePositions).mockReturnValue({
      activePositions: ref([
        {
          positionId: 1n,
          owner: '0x456',
          harbDeposit: 100n,
          taxRate: 0.02,
          taxRateIndex: 1,
          iAmOwner: false,
        },
      ]) as Ref<Partial<Position>[]>,
    } as MockPositionsReturn);

    vi.mocked(useStake).mockReturnValue({
      stakingAmountShares: 100n,
      taxRateIndex: 0,
    } as MockStakeReturn);

    const { snatchablePositions } = useSnatchSelection();

    // Wait for watchEffect to run
    await new Promise(resolve => setTimeout(resolve, 0));

    // Position with taxRateIndex 1 should be filtered out when selecting taxRateIndex 0
    expect(snatchablePositions.value).toEqual([]);
  });

  it('should filter out owned positions by default', async () => {
    vi.mocked(usePositions).mockReturnValue({
      activePositions: ref([
        {
          positionId: 1n,
          owner: '0x123',
          harbDeposit: 100n,
          taxRate: 0.5,
          taxRateIndex: 1,
          iAmOwner: true,
        },
      ]) as Ref<Partial<Position>[]>,
    } as MockPositionsReturn);

    const { snatchablePositions } = useSnatchSelection();
    await new Promise(resolve => setTimeout(resolve, 0));
    expect(snatchablePositions.value).toEqual([]);
  });

  it('should include owned positions when demo mode is enabled', async () => {
    const position = {
      positionId: 1n,
      owner: '0x123',
      harbDeposit: 100n,
      taxRate: 0.01, // 1% tax rate (index 0)
      taxRateIndex: 0,
      iAmOwner: true,
    };

    vi.mocked(usePositions).mockReturnValue({
      activePositions: ref([position]),
    } as MockPositionsReturn);

    vi.mocked(useStake).mockReturnValue({
      stakingAmountShares: 100n,
      taxRateIndex: 1, // Corresponds to 2% decimal (index 1)
    } as MockStakeReturn);

    // Need outstandingStake > stakingAmountShares to create shortfall
    vi.mocked(useStatCollection).mockReturnValue({
      stakeTotalSupply: 10000000000000000000000000n,
      kraikenTotalSupply: 10000000000000000000000000n,
      outstandingStake: 500n,
    } as MockStatCollectionReturn);

    const { snatchablePositions } = useSnatchSelection(true);

    // Wait for watchEffect to run (no longer async)
    await nextTick();

    expect(snatchablePositions.value).toContainEqual(position);
  });

  it('should handle partial fills', async () => {
    const position1 = {
      positionId: 1n,
      owner: '0x456',
      harbDeposit: 100n,
      taxRate: 0.01, // 1% tax rate (index 0)
      taxRateIndex: 0,
      iAmOwner: false,
    };

    const position2 = {
      positionId: 2n,
      owner: '0x789',
      harbDeposit: 200n,
      taxRate: 0.02, // 2% tax rate (index 1)
      taxRateIndex: 1,
      iAmOwner: false,
    };

    vi.mocked(usePositions).mockReturnValue({
      activePositions: ref([position1, position2]) as Ref<Position[]>,
    } as MockPositionsReturn);

    vi.mocked(useStake).mockReturnValue({
      stakingAmountShares: 150n,
      taxRateIndex: 2, // Corresponds to 3% decimal (index 2)
    } as MockStakeReturn);

    // Need outstandingStake > stakingAmountShares to create shortfall
    vi.mocked(useStatCollection).mockReturnValue({
      stakeTotalSupply: 10000000000000000000000000n,
      kraikenTotalSupply: 10000000000000000000000000n,
      outstandingStake: 500n,
    } as MockStatCollectionReturn);

    const { snatchablePositions } = useSnatchSelection();

    // Wait for watchEffect to run
    await nextTick();

    expect(snatchablePositions.value).toEqual([position1, position2]);
  });

  it('should update floor tax based on selected positions', async () => {
    const position = {
      positionId: 1n,
      owner: '0x456',
      harbDeposit: 100n,
      taxRate: 0.02, // 2% tax rate (index 1)
      taxRateIndex: 1,
      iAmOwner: false,
    };

    vi.mocked(usePositions).mockReturnValue({
      activePositions: ref([position]),
    } as MockPositionsReturn);

    vi.mocked(useStake).mockReturnValue({
      stakingAmountShares: 100n,
      taxRateIndex: 2, // Corresponds to 3% decimal (index 2)
    } as MockStakeReturn);

    // Need outstandingStake > stakingAmountShares to create shortfall
    vi.mocked(useStatCollection).mockReturnValue({
      stakeTotalSupply: 10000000000000000000000000n,
      kraikenTotalSupply: 10000000000000000000000000n,
      outstandingStake: 500n,
    } as MockStatCollectionReturn);

    vi.mocked(useAdjustTaxRate).mockReturnValue({
      taxRates: [
        { index: 0, year: 1, daily: 0.00274, decimal: 0.01 },
        { index: 1, year: 2, daily: 0.00548, decimal: 0.02 },
        { index: 2, year: 3, daily: 0.00822, decimal: 0.03 },
      ],
    } as MockAdjustTaxRateReturn);

    const { floorTax } = useSnatchSelection();

    // Wait for watchEffect to run
    await nextTick();

    // Floor tax should be taxRates[maxSelectedTaxRateIndex + 1]
    // Position has taxRateIndex: 1, so nextIndex = 2, taxRates[2] = { year: 3 }
    expect(floorTax.value).toBe(3);
  });
});
