import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { printAsset, accountBalance, waitForNextOpen, runDay } from './trading';
import type { Alpaca } from './alpaca';

function mockAlpaca(overrides: Partial<Alpaca> = {}): Alpaca {
    return {
        getAccount: vi.fn(),
        getAssets: vi.fn(),
        getAsset: vi.fn(),
        getClock: vi.fn(),
        getLatestQuote: vi.fn(),
        getLatestTrades: vi.fn(),
        ...overrides,
    } as unknown as Alpaca;
}

beforeEach(() => {
    vi.useFakeTimers();
});

afterEach(() => {
    vi.useRealTimers();
    vi.restoreAllMocks();
});

describe('printAsset', () => {
    beforeEach(() => {
        vi.spyOn(console, 'log').mockImplementation(() => {});
    });

    it('logs fractional when asset is fractionable', async () => {
        const alpaca = mockAlpaca({
            getAsset: vi.fn().mockResolvedValue({ symbol: 'TQQQ', fractionable: true, status: 'active', asset_class: 'us_equity' }),
        });

        await printAsset(alpaca, 'TQQQ');
        expect(console.log).toHaveBeenCalledWith('TQQQ is fractional');
    });

    it('logs not fractional when asset is not fractionable', async () => {
        const alpaca = mockAlpaca({
            getAsset: vi.fn().mockResolvedValue({ symbol: 'BRK.A', fractionable: false, status: 'active', asset_class: 'us_equity' }),
        });

        await printAsset(alpaca, 'BRK.A');
        expect(console.log).toHaveBeenCalledWith('BRK.A is not fractional');
    });
});

describe('accountBalance', () => {
    it('returns the cash value from the account', async () => {
        const alpaca = mockAlpaca({
            getAccount: vi.fn().mockResolvedValue({ cash: '10000.00' }),
        });

        const result = await accountBalance(alpaca);
        expect(result).toBe('10000.00');
    });
});

describe('waitForNextOpen', () => {
    it('calls getClock and waits until next_open', async () => {
        const futureDate = new Date(Date.now() + 60000).toISOString();
        const alpaca = mockAlpaca({
            getClock: vi.fn().mockResolvedValue({ is_open: false, next_open: futureDate, next_close: futureDate }),
        });

        const promise = waitForNextOpen(alpaca);
        await vi.advanceTimersByTimeAsync(60000);
        await promise;

        expect(alpaca.getClock).toHaveBeenCalled();
    });
});

describe('runDay', () => {
    beforeEach(() => {
        vi.spyOn(console, 'log').mockImplementation(() => {});
    });

    it('logs up day when second quote ask price is higher', async () => {
        const alpaca = mockAlpaca({
            getClock: vi.fn().mockResolvedValue({ is_open: false, next_open: new Date().toISOString(), next_close: new Date().toISOString() }),
            getLatestQuote: vi.fn()
                .mockResolvedValueOnce({ ap: 50.00, bp: 49.90 })
                .mockResolvedValueOnce({ ap: 50.50, bp: 50.40 }),
        });

        const promise = runDay(alpaca);
        await vi.advanceTimersByTimeAsync(61000);
        await promise;

        expect(console.log).toHaveBeenCalledWith('up day: ', expect.any(Date));
    });

    it('logs down day when second quote ask price is lower', async () => {
        const alpaca = mockAlpaca({
            getClock: vi.fn().mockResolvedValue({ is_open: false, next_open: new Date().toISOString(), next_close: new Date().toISOString() }),
            getLatestQuote: vi.fn()
                .mockResolvedValueOnce({ ap: 50.00, bp: 49.90 })
                .mockResolvedValueOnce({ ap: 49.50, bp: 49.40 }),
        });

        const promise = runDay(alpaca);
        await vi.advanceTimersByTimeAsync(61000);
        await promise;

        expect(console.log).toHaveBeenCalledWith('down day', expect.any(Date));
    });

    it('logs down day when prices are equal', async () => {
        const alpaca = mockAlpaca({
            getClock: vi.fn().mockResolvedValue({ is_open: false, next_open: new Date().toISOString(), next_close: new Date().toISOString() }),
            getLatestQuote: vi.fn()
                .mockResolvedValueOnce({ ap: 50.00, bp: 49.90 })
                .mockResolvedValueOnce({ ap: 50.00, bp: 49.90 }),
        });

        const promise = runDay(alpaca);
        await vi.advanceTimersByTimeAsync(61000);
        await promise;

        expect(console.log).toHaveBeenCalledWith('down day', expect.any(Date));
    });
});
