import type { Workspace } from '../hooks/useTabs';
import type { MappingsUpdatedEvent } from '../types/mappings';
import type {
  BalanceSheetPanelData,
  CashFlowPanelData,
  EarningsPanelData,
  FinancialsPanelData,
} from '../types/financial';
import type { TerminalCommandResponse } from '../types/terminal';

interface RefreshablePanelBase {
  workspaceId: string;
  panelEntryId: string;
  sourceCommand: string;
  ticker: string;
  frequency: 'annual' | 'quarterly';
  currency?: string;
  periodStartDate?: string;
  periodEndDate?: string;
}

export interface RefreshableEarningsPanel extends RefreshablePanelBase {
  start?: string;
  end?: string;
}

interface RefreshAffectedEarningsPanelsArgs {
  workspaces: Workspace[];
  event: MappingsUpdatedEvent;
  execute: (
    panel: RefreshableEarningsPanel,
  ) => Promise<TerminalCommandResponse>;
  replacePanel: (
    panel: RefreshableEarningsPanel,
    response: TerminalCommandResponse,
  ) => void;
  reportFailure: (panel: RefreshableEarningsPanel, reason: string) => void;
  inFlightKeys: Set<string>;
}

export interface RefreshableFinancialsPanel extends RefreshablePanelBase {
  start?: string;
  end?: string;
}

interface RefreshAffectedFinancialsPanelsArgs {
  workspaces: Workspace[];
  event: MappingsUpdatedEvent;
  execute: (
    panel: RefreshableFinancialsPanel,
  ) => Promise<TerminalCommandResponse>;
  replacePanel: (
    panel: RefreshableFinancialsPanel,
    response: TerminalCommandResponse,
  ) => void;
  reportFailure: (panel: RefreshableFinancialsPanel, reason: string) => void;
  inFlightKeys: Set<string>;
}

export const getRefreshableEarningsPanel = (
  workspace: Workspace,
): RefreshableEarningsPanel | null => {
  for (let index = workspace.terminal.history.length - 1; index >= 0; index -= 1) {
    const entry = workspace.terminal.history[index];
    if (entry.type !== 'panel' || typeof entry.content === 'string') {
      continue;
    }
    if (entry.content.type !== 'earnings') {
      continue;
    }

    const sourceCommand = findSourceCommand(workspace, index);
    if (!sourceCommand) {
      return null;
    }

    const parsed = parseEarningsSourceCommand(sourceCommand);
    if (!parsed) {
      return null;
    }

    const range = derivePanelRange(entry.content.data);
    const currency =
      parsed.currency ?? entry.content.data.selectedCurrency ?? undefined;
    const start = parsed.start ?? entry.content.data.selectedPeriodStart ?? undefined;
    const end = parsed.end ?? entry.content.data.selectedPeriodEnd ?? undefined;

    return {
      workspaceId: workspace.id,
      panelEntryId: entry.id,
      sourceCommand: buildEarningsRefreshCommand(
        parsed.ticker,
        parsed.frequency,
        start,
        end,
        currency,
      ),
      ticker: parsed.ticker,
      frequency: parsed.frequency,
      start,
      end,
      currency,
      periodStartDate: range.periodStartDate,
      periodEndDate: range.periodEndDate,
    };
  }

  return null;
};

export const shouldRefreshEarningsPanel = (
  panel: RefreshableEarningsPanel,
  event: MappingsUpdatedEvent,
): boolean => {
  return shouldRefreshPanel(panel, event);
};

export const getRefreshableFinancialsPanel = (
  workspace: Workspace,
): RefreshableFinancialsPanel | null => {
  for (let index = workspace.terminal.history.length - 1; index >= 0; index -= 1) {
    const entry = workspace.terminal.history[index];
    if (entry.type !== 'panel' || typeof entry.content === 'string') {
      continue;
    }
    if (
      entry.content.type !== 'financials' &&
      entry.content.type !== 'balanceSheet' &&
      entry.content.type !== 'cashFlow'
    ) {
      continue;
    }

    const sourceCommand = findSourceCommand(workspace, index);
    if (!sourceCommand) {
      return null;
    }

    const parsed = parseStatementSourceCommand(sourceCommand);
    if (!parsed) {
      return null;
    }

    const range = derivePanelRange(entry.content.data);
    const currency =
      parsed.currency ??
      (entry.content.type === 'balanceSheet'
        ? entry.content.data.selectedCurrency
        : undefined);
    const start =
      parsed.start ??
      (entry.content.type !== 'financials'
        ? entry.content.data.selectedPeriodStart
        : undefined);
    const end =
      parsed.end ??
      (entry.content.type !== 'financials'
        ? entry.content.data.selectedPeriodEnd
        : undefined);

    return {
      workspaceId: workspace.id,
      panelEntryId: entry.id,
      sourceCommand: buildStatementRefreshCommand(
        parsed.command,
        parsed.ticker,
        parsed.frequency,
        start,
        end,
        currency,
      ),
      ticker: parsed.ticker,
      frequency: parsed.frequency,
      start,
      end,
      currency,
      periodStartDate: range.periodStartDate,
      periodEndDate: range.periodEndDate,
    };
  }

  return null;
};

export const shouldRefreshFinancialsPanel = (
  panel: RefreshableFinancialsPanel,
  event: MappingsUpdatedEvent,
): boolean => {
  return shouldRefreshPanel(panel, event);
};

const shouldRefreshPanel = (
  panel: RefreshablePanelBase,
  event: MappingsUpdatedEvent,
): boolean => {
  if (event.refreshScope.fullRefresh) {
    return true;
  }

  return event.refreshScope.affectedScopes.some((scope) => {
    if (
      scope.ticker &&
      scope.ticker.trim().toUpperCase() !== panel.ticker.toUpperCase()
    ) {
      return false;
    }

    if (!scope.periodStart && !scope.periodEnd) {
      return true;
    }

    return scopeOverlapsPanel(panel, scope.periodStart, scope.periodEnd);
  });
};

export const refreshPanelKey = (panel: RefreshableEarningsPanel): string =>
  `${panel.workspaceId}:${panel.sourceCommand}`;

export const refreshAffectedEarningsPanels = async ({
  workspaces,
  event,
  execute,
  replacePanel,
  reportFailure,
  inFlightKeys,
}: RefreshAffectedEarningsPanelsArgs): Promise<void> => {
  const panels = workspaces
    .map(getRefreshableEarningsPanel)
    .filter((panel): panel is RefreshableEarningsPanel => panel !== null)
    .filter((panel) => shouldRefreshEarningsPanel(panel, event));

  await Promise.all(
    panels.map(async (panel) => {
      const key = refreshPanelKey(panel);
      if (inFlightKeys.has(key)) {
        return;
      }

      inFlightKeys.add(key);
      try {
        const response = await execute(panel);
        if (response.kind === 'panel' && response.panel.type === 'earnings') {
          replacePanel(panel, response);
          return;
        }

        reportFailure(
          panel,
          'Failed to refresh earnings after mapping update.',
        );
      } catch (error) {
        reportFailure(
          panel,
          error instanceof Error
            ? `Failed to refresh earnings after mapping update: ${error.message}`
            : 'Failed to refresh earnings after mapping update.',
        );
      } finally {
        inFlightKeys.delete(key);
      }
    }),
  );
};

export const refreshAffectedFinancialsPanels = async ({
  workspaces,
  event,
  execute,
  replacePanel,
  reportFailure,
  inFlightKeys,
}: RefreshAffectedFinancialsPanelsArgs): Promise<void> => {
  const panels = workspaces
    .map(getRefreshableFinancialsPanel)
    .filter((panel): panel is RefreshableFinancialsPanel => panel !== null)
    .filter((panel) => shouldRefreshFinancialsPanel(panel, event));

  await Promise.all(
    panels.map(async (panel) => {
      const key = refreshPanelKey(panel);
      if (inFlightKeys.has(key)) {
        return;
      }

      inFlightKeys.add(key);
      try {
        const response = await execute(panel);
        if (
          response.kind === 'panel' &&
          (response.panel.type === 'financials' ||
            response.panel.type === 'balanceSheet')
        ) {
          replacePanel(panel, response);
          return;
        }

        reportFailure(
          panel,
          'Failed to refresh financials after mapping update.',
        );
      } catch (error) {
        reportFailure(
          panel,
          error instanceof Error
            ? `Failed to refresh financials after mapping update: ${error.message}`
            : 'Failed to refresh financials after mapping update.',
        );
      } finally {
        inFlightKeys.delete(key);
      }
    }),
  );
};

const parseEarningsSourceCommand = (
  command: string,
):
  | {
      command: '/em';
      ticker: string;
      frequency: 'annual' | 'quarterly';
      start?: string;
      end?: string;
      currency?: string;
    }
  | null => {
  const parts = command.trim().split(/\s+/);
  if (parts[0] !== '/em' || !parts[1]) {
    return null;
  }

  const frequency =
    (parts[2] as 'annual' | 'quarterly' | undefined) ?? 'annual';
  if (parts[2] && parts[2] !== 'annual' && parts[2] !== 'quarterly') {
    return null;
  }
  const remaining = parts.slice(parts[2] === 'annual' || parts[2] === 'quarterly' ? 3 : 2);
  const parsedTail = parseCommandCurrencyTail(remaining);
  if (!parsedTail || (parsedTail.positionals.length !== 0 && parsedTail.positionals.length !== 2)) {
    return null;
  }

  return {
    command: '/em',
    ticker: parts[1].toUpperCase(),
    frequency,
    start: parsedTail.positionals[0],
    end: parsedTail.positionals[1],
    currency: parsedTail.currency,
  };
};

const parseStatementSourceCommand = (
  command: string,
):
  | {
      command: '/fa' | '/bs' | '/cf';
      ticker: string;
      frequency: 'annual' | 'quarterly';
      start?: string;
      end?: string;
      currency?: string;
    }
  | null => {
  const parts = command.trim().split(/\s+/);
  if ((parts[0] !== '/fa' && parts[0] !== '/bs' && parts[0] !== '/cf') || !parts[1]) {
    return null;
  }

  const frequency =
    (parts[2] as 'annual' | 'quarterly' | undefined) ?? 'annual';
  if (parts[2] && parts[2] !== 'annual' && parts[2] !== 'quarterly') {
    return null;
  }
  const remaining = parts.slice(parts[2] === 'annual' || parts[2] === 'quarterly' ? 3 : 2);
  const parsedTail = parseCommandCurrencyTail(remaining);
  if (!parsedTail) {
    return null;
  }

  const { positionals, currency } = parsedTail;
  const [start, end] = positionals;

  return {
    command: parts[0] as '/fa' | '/bs' | '/cf',
    ticker: parts[1].toUpperCase(),
    frequency,
    start: start || undefined,
    end: end || undefined,
    currency,
  };
};

const parseCommandCurrencyTail = (
  tokens: string[],
): { positionals: string[]; currency?: string } | null => {
  const currencyIndex = tokens.findIndex(
    (token) => token.toLowerCase() === '--currency',
  );
  if (currencyIndex === -1) {
    return { positionals: tokens };
  }
  if (currencyIndex + 2 !== tokens.length) {
    return null;
  }

  const currency = tokens[currencyIndex + 1]?.toUpperCase();
  if (!currency) {
    return null;
  }

  return {
    positionals: tokens.slice(0, currencyIndex),
    currency,
  };
};

const buildEarningsRefreshCommand = (
  ticker: string,
  frequency: 'annual' | 'quarterly',
  start?: string,
  end?: string,
  currency?: string,
) => {
  const parts = ['/em', ticker, frequency];
  if (start && end) {
    parts.push(start, end);
  }
  if (currency) {
    parts.push('--currency', currency);
  }
  return parts.join(' ');
};

const buildStatementRefreshCommand = (
  command: '/fa' | '/bs' | '/cf',
  ticker: string,
  frequency: 'annual' | 'quarterly',
  start?: string,
  end?: string,
  currency?: string,
) => {
  const parts = [command, ticker, frequency];
  if (start && end) {
    parts.push(start, end);
  }
  if (command === '/bs' && currency) {
    parts.push('--currency', currency);
  }
  return parts.join(' ');
};

const findSourceCommand = (
  workspace: Workspace,
  panelIndex: number,
): string | null => {
  for (let index = panelIndex - 1; index >= 0; index -= 1) {
    const candidate = workspace.terminal.history[index];
    if (
      candidate.type === 'command' &&
      typeof candidate.content === 'string' &&
      candidate.content.startsWith('/')
    ) {
      return candidate.content;
    }
  }

  return null;
};

const derivePanelRange = (
  data: EarningsPanelData | FinancialsPanelData | BalanceSheetPanelData | CashFlowPanelData,
): { periodStartDate?: string; periodEndDate?: string } => {
  const periodEnds = data.periods
    .map((period) => period.periodEnd)
    .filter((value): value is string => Boolean(value))
    .sort();

  return {
    periodStartDate: periodEnds[0],
    periodEndDate: periodEnds[periodEnds.length - 1],
  };
};

const scopeOverlapsPanel = (
  panel: RefreshableEarningsPanel,
  periodStart?: string,
  periodEnd?: string,
): boolean => {
  if (!panel.periodStartDate || !panel.periodEndDate) {
    return true;
  }

  const panelStart = parseIsoDate(panel.periodStartDate);
  const panelEnd = parseIsoDate(panel.periodEndDate);
  const scopeStart = periodStart ? parseIsoDate(periodStart) : null;
  const scopeEnd = periodEnd ? parseIsoDate(periodEnd) : null;

  if (
    panelStart === null ||
    panelEnd === null ||
    (periodStart && scopeStart === null) ||
    (periodEnd && scopeEnd === null)
  ) {
    return true;
  }

  const effectiveScopeStart = scopeStart ?? Number.NEGATIVE_INFINITY;
  const effectiveScopeEnd = scopeEnd ?? Number.POSITIVE_INFINITY;

  return effectiveScopeStart <= panelEnd && panelStart <= effectiveScopeEnd;
};

const parseIsoDate = (value: string): number | null => {
  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? null : parsed;
};
