import { describe, expect, it, mock } from 'bun:test';
import {
  getRefreshableEarningsPanel,
  refreshAffectedEarningsPanels,
  refreshPanelKey,
  shouldRefreshEarningsPanel,
} from './mappingsRefresh';
import type { Workspace } from '../hooks/useTabs';
import type { EarningsPanelData } from '../types/financial';
import type { MappingsUpdatedEvent } from '../types/mappings';
import type { TerminalCommandResponse } from '../types/terminal';

const makeEarningsPanel = (
  symbol: string,
  overrides: Partial<EarningsPanelData> = {},
): EarningsPanelData => ({
  symbol,
  companyName: `${symbol} Inc.`,
  cik: `cik-${symbol}`,
  frequency: 'annual',
  latestFiling: {
    accessionNumber: `${symbol}-10k`,
    filingDate: '2025-02-01',
    form: '10-K',
  },
  sourceStatus: {
    companyfactsUsed: false,
  },
  availableConcepts: [],
  availablePeriods: [
    {
      id: '2023',
      label: 'FY2023',
      frequency: 'annual',
      fiscalYear: '2023',
      fiscalPeriod: 'FY',
      periodEnd: '2023-12-31',
      sortKey: '2023',
    },
    {
      id: '2024',
      label: 'FY2024',
      frequency: 'annual',
      fiscalYear: '2024',
      fiscalPeriod: 'FY',
      periodEnd: '2024-12-31',
      sortKey: '2024',
    },
  ],
  selectedPeriodStart: '2023',
  selectedPeriodEnd: '2024',
  periods: [
    {
      label: 'FY2024',
      displayLabel: 'FY2024',
      periodEnd: '2024-12-31',
      filedDate: '2025-02-01',
      form: '10-K',
      totalRevenues: 100,
    },
    {
      label: 'FY2023',
      displayLabel: 'FY2023',
      periodEnd: '2023-12-31',
      filedDate: '2024-02-01',
      form: '10-K',
      totalRevenues: 90,
    },
  ],
  ...overrides,
});

const makeWorkspace = (
  id: string,
  sourceCommand = '/em AAPL annual 2023 2024',
  panel = makeEarningsPanel('AAPL'),
): Workspace => ({
  id,
  name: id,
  createdAt: new Date('2026-04-13T10:00:00Z'),
  history: [
    {
      id: `${id}-command`,
      type: 'command',
      content: sourceCommand,
      timestamp: new Date('2026-04-13T10:00:00Z'),
    },
    {
      id: `${id}-panel`,
      type: 'panel',
      content: { type: 'earnings', data: panel },
      timestamp: new Date('2026-04-13T10:00:01Z'),
    },
  ],
});

const makeEvent = (
  overrides: Partial<MappingsUpdatedEvent> = {},
): MappingsUpdatedEvent => ({
  changeKind: 'created',
  refreshScope: {
    fullRefresh: false,
    affectedScopes: [{ ticker: 'AAPL' }],
    affectedTargets: ['total_revenues'],
  },
  ...overrides,
});

describe('mappings refresh targeting', () => {
  it('extracts the latest refreshable earnings panel from workspace history', () => {
    const panel = getRefreshableEarningsPanel(makeWorkspace('workspace-aapl'));

    expect(panel).not.toBeNull();
    expect(panel?.sourceCommand).toBe('/em AAPL annual 2023 2024');
    expect(panel?.ticker).toBe('AAPL');
    expect(panel?.periodStartDate).toBe('2023-12-31');
    expect(panel?.periodEndDate).toBe('2024-12-31');
  });

  it('refreshes matching tickers and overlapping period scopes only', () => {
    const panel = getRefreshableEarningsPanel(makeWorkspace('workspace-aapl'));
    if (!panel) {
      throw new Error('Expected refreshable panel');
    }

    expect(
      shouldRefreshEarningsPanel(
        panel,
        makeEvent({
          refreshScope: {
            fullRefresh: false,
            affectedScopes: [
              {
                ticker: 'AAPL',
                periodStart: '2024-01-01',
                periodEnd: '2024-12-31',
              },
            ],
            affectedTargets: ['gross_profit'],
          },
        }),
      ),
    ).toBe(true);

    expect(
      shouldRefreshEarningsPanel(
        panel,
        makeEvent({
          refreshScope: {
            fullRefresh: false,
            affectedScopes: [
              {
                ticker: 'MSFT',
              },
            ],
            affectedTargets: ['gross_profit'],
          },
        }),
      ),
    ).toBe(false);

    expect(
      shouldRefreshEarningsPanel(
        panel,
        makeEvent({
          refreshScope: {
            fullRefresh: false,
            affectedScopes: [
              {
                ticker: 'AAPL',
                periodStart: '2021-01-01',
                periodEnd: '2021-12-31',
              },
            ],
            affectedTargets: ['gross_profit'],
          },
        }),
      ),
    ).toBe(false);
  });

  it('treats imported full refresh events as matching every earnings panel', () => {
    const panel = getRefreshableEarningsPanel(makeWorkspace('workspace-aapl'));
    if (!panel) {
      throw new Error('Expected refreshable panel');
    }

    expect(
      shouldRefreshEarningsPanel(
        panel,
        makeEvent({
          changeKind: 'imported',
          refreshScope: {
            fullRefresh: true,
            affectedScopes: [],
            affectedTargets: [],
          },
        }),
      ),
    ).toBe(true);
  });
});

describe('refreshAffectedEarningsPanels', () => {
  it('replaces affected earnings panels in place after a successful refresh', async () => {
    const workspace = makeWorkspace('workspace-aapl');
    const execute = mock(
      async (): Promise<TerminalCommandResponse> => ({
        kind: 'panel',
        panel: {
          type: 'earnings',
          data: makeEarningsPanel('AAPL', {
            periods: [
              {
                label: 'FY2024',
                displayLabel: 'FY2024',
                periodEnd: '2024-12-31',
                filedDate: '2025-02-01',
                form: '10-K',
                totalRevenues: 123,
              },
            ],
          }),
        },
      }),
    );
    const replacePanel = mock(() => {});
    const reportFailure = mock(() => {});

    await refreshAffectedEarningsPanels({
      workspaces: [workspace],
      event: makeEvent(),
      execute,
      replacePanel,
      reportFailure,
      inFlightKeys: new Set<string>(),
    });

    expect(execute).toHaveBeenCalledTimes(1);
    expect(replacePanel).toHaveBeenCalledTimes(1);
    expect(reportFailure).not.toHaveBeenCalled();
  });

  it('reports failures when the refresh does not return an earnings panel', async () => {
    const workspace = makeWorkspace('workspace-aapl');
    const panel = getRefreshableEarningsPanel(workspace);
    if (!panel) {
      throw new Error('Expected refreshable panel');
    }
    const execute = mock(
      async (): Promise<TerminalCommandResponse> => ({
        kind: 'text',
        content: 'unexpected',
      }),
    );
    const replacePanel = mock(() => {});
    const reportFailure = mock(() => {});

    await refreshAffectedEarningsPanels({
      workspaces: [workspace],
      event: makeEvent(),
      execute,
      replacePanel,
      reportFailure,
      inFlightKeys: new Set<string>(),
    });

    expect(replacePanel).not.toHaveBeenCalled();
    expect(reportFailure).toHaveBeenCalledTimes(1);
    expect(reportFailure).toHaveBeenNthCalledWith(
      1,
      panel,
      'Failed to refresh earnings after mapping update.',
    );
  });

  it('deduplicates refreshes that are already in flight', async () => {
    const workspace = makeWorkspace('workspace-aapl');
    const panel = getRefreshableEarningsPanel(workspace);
    if (!panel) {
      throw new Error('Expected refreshable panel');
    }

    const execute = mock(async (): Promise<TerminalCommandResponse> => {
      throw new Error('should not run');
    });
    const replacePanel = mock(() => {});
    const reportFailure = mock(() => {});
    const inFlightKeys = new Set<string>([refreshPanelKey(panel)]);

    await refreshAffectedEarningsPanels({
      workspaces: [workspace],
      event: makeEvent(),
      execute,
      replacePanel,
      reportFailure,
      inFlightKeys,
    });

    expect(execute).not.toHaveBeenCalled();
    expect(replacePanel).not.toHaveBeenCalled();
    expect(reportFailure).not.toHaveBeenCalled();
  });
});
