import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
import type { FastifyBaseLogger } from 'fastify';
import type { OHLCService } from '../../services/ohlc-service.js';
import type { WorkspaceManager } from '../../workspace/workspace-manager.js';
import type { ChartState } from '../../workspace/types.js';
import * as chrono from 'chrono-node';

/**
 * Get Chart Data Tool
 *
 * Standard LangChain tool for fetching OHLCV+ data with workspace defaults.
 * Allows agent to override any parameter for historical or alternative ticker queries.
 */

export interface GetChartDataToolConfig {
  ohlcService: OHLCService;
  workspaceManager: WorkspaceManager;
  logger: FastifyBaseLogger;
}

export function createGetChartDataTool(config: GetChartDataToolConfig): DynamicStructuredTool {
  const { ohlcService, workspaceManager, logger } = config;

  return new DynamicStructuredTool({
    name: 'GetChartData',
    description: `Fetch OHLCV+ data for current chart or any ticker/timeframe. All parameters are optional and default to workspace chart state.

**IMPORTANT: Use this tool ONLY for quick, casual data viewing. For any analysis, plotting, statistics, or deep research, use the 'research' tool instead.**

**Hard limit: returns at most 500 bars (the most recent 500). This tool is not suitable for analysis requiring longer sequences — use the 'research' tool for that.**

Parameters:
- ticker (optional): Market symbol in SYMBOL.EXCHANGE format, e.g. "BTC/USDT.BINANCE" (defaults to workspace chartState.symbol)
- period (optional): OHLC period in seconds (defaults to workspace chartState.period)
- from_time (optional): Start time as Unix timestamp (number or string like "1774126800") OR date string like "2 days ago", "2024-01-01" (defaults to workspace chartState.start_time)
- to_time (optional): End time as Unix timestamp (number or string like "1774732500") OR date string like "now", "yesterday" (defaults to workspace chartState.end_time)
- countback (optional): Limit number of bars returned (max 500)
- columns (optional): Extra columns beyond OHLC: ["volume", "buy_vol", "sell_vol", "open_time", "high_time", "low_time", "close_time", "open_interest"]`,
    schema: z.object({
      ticker: z.string().optional().describe('Market symbol (defaults to workspace chartState.symbol)'),
      period: z.number().optional().describe('OHLC period in seconds (defaults to workspace chartState.period)'),
      from_time: z.union([z.number(), z.string()]).optional().describe('Start time: Unix seconds OR date string (defaults to workspace chartState.start_time)'),
      to_time: z.union([z.number(), z.string()]).optional().describe('End time: Unix seconds OR date string (defaults to workspace chartState.end_time)'),
      countback: z.number().optional().describe('Limit number of bars returned (max 500)'),
      columns: z.array(z.enum(['volume', 'buy_vol', 'sell_vol', 'open_time', 'high_time', 'low_time', 'close_time', 'open_interest'])).optional().describe('Extra columns beyond OHLC'),
    }),
    func: async ({ ticker, period, from_time, to_time, countback, columns }) => {
      const MAX_BARS = 500;
      // Enforce hard cap — never return more than MAX_BARS bars
      const effectiveCountback = countback !== undefined ? Math.min(countback, MAX_BARS) : MAX_BARS;

      logger.debug({ ticker, period, from_time, to_time, countback: effectiveCountback, columns }, 'Executing GetChartData tool');

      try {
        // Get workspace chart state
        const chartState = await getChartState(workspaceManager, logger);

        // Build request with workspace defaults
        const finalTicker = ticker ?? chartState.symbol;
        const finalPeriod = period ?? chartState.period;
        const finalFromTime = await parseTime(from_time, chartState.start_time, logger);
        const finalToTime = await parseTime(to_time, chartState.end_time, logger);
        const requestedColumns = columns ?? [];

        // Validate we have all required parameters
        if (!finalTicker) {
          return JSON.stringify({ error: 'Ticker not specified and not available in workspace' });
        }
        if (!finalPeriod) {
          return JSON.stringify({ error: 'Period not specified and not available in workspace' });
        }
        if (!finalFromTime) {
          return JSON.stringify({ error: 'from_time not specified and not available in workspace' });
        }
        if (!finalToTime) {
          return JSON.stringify({ error: 'to_time not specified and not available in workspace' });
        }

        logger.debug({
          ticker: finalTicker,
          period: finalPeriod,
          from_time: finalFromTime,
          to_time: finalToTime,
          countback,
          columns: requestedColumns,
        }, 'Fetching OHLC data');

        // Fetch data from OHLCService
        const historyResult = await ohlcService.fetchOHLC(
          finalTicker,
          finalPeriod,
          finalFromTime,
          finalToTime,
          effectiveCountback
        );

        if (historyResult.noData || !historyResult.bars || historyResult.bars.length === 0) {
          return JSON.stringify({
            ticker: finalTicker,
            period: finalPeriod,
            timeRange: { start: finalFromTime, end: finalToTime },
            bars: [],
          });
        }

        // Enforce hard cap — keep the most recent bars
        const sourceBars = historyResult.bars.length > MAX_BARS
          ? historyResult.bars.slice(-MAX_BARS)
          : historyResult.bars;

        // Filter/format bars with requested columns
        const bars = sourceBars.map(bar => {
          const result: any = {
            time: bar.time,
            open: bar.open,
            high: bar.high,
            low: bar.low,
            close: bar.close,
            ticker: finalTicker,
          };

          // Add optional columns if requested
          for (const col of requestedColumns) {
            if (col === 'volume' && bar.volume !== undefined) {
              result.volume = bar.volume;
            } else if (col === 'buy_vol' && bar.buy_vol !== undefined) {
              result.buy_vol = bar.buy_vol;
            } else if (col === 'sell_vol' && bar.sell_vol !== undefined) {
              result.sell_vol = bar.sell_vol;
            } else if (col === 'open_time' && bar.open_time !== undefined) {
              result.open_time = bar.open_time;
            } else if (col === 'high_time' && bar.high_time !== undefined) {
              result.high_time = bar.high_time;
            } else if (col === 'low_time' && bar.low_time !== undefined) {
              result.low_time = bar.low_time;
            } else if (col === 'close_time' && bar.close_time !== undefined) {
              result.close_time = bar.close_time;
            } else if (col === 'open_interest' && bar.open_interest !== undefined) {
              result.open_interest = bar.open_interest;
            }
          }

          return result;
        });

        logger.info({ ticker: finalTicker, barCount: bars.length }, 'Chart data fetched successfully');

        return JSON.stringify({
          ticker: finalTicker,
          period: finalPeriod,
          timeRange: {
            start: finalFromTime,
            end: finalToTime,
          },
          bars,
        });
      } catch (error) {
        logger.error({ error }, 'Get chart data tool failed');
        return JSON.stringify({
          error: error instanceof Error ? error.message : String(error),
        });
      }
    },
  });
}

/**
 * Get chart state from workspace
 */
async function getChartState(workspaceManager: WorkspaceManager, logger: FastifyBaseLogger): Promise<ChartState> {
  try {
    const chartState = workspaceManager.getState<ChartState>('chartState');

    if (!chartState) {
      // Return default chart state
      return {
        symbol: 'BTC/USDT.BINANCE',
        start_time: null,
        end_time: null,
        period: 900,
        selected_shapes: [],
      };
    }

    return chartState;
  } catch (error) {
    logger.error({ error }, 'Failed to get chart state from workspace');
    // Return default chart state
    return {
      symbol: 'BTC/USDT.BINANCE',
      start_time: null,
      end_time: null,
      period: 900,
      selected_shapes: [],
    };
  }
}

/**
 * Parse time parameter (Unix seconds, date string, or null)
 * Returns Unix timestamp in seconds
 */
async function parseTime(
  timeParam: number | string | null | undefined,
  workspaceDefault: number | null,
  logger: FastifyBaseLogger
): Promise<number | null> {
  // Use workspace default if param not provided
  if (timeParam === undefined || timeParam === null) {
    return workspaceDefault;
  }

  // If it's already a number, assume Unix seconds
  if (typeof timeParam === 'number') {
    return timeParam;
  }

  // Try to parse string as numeric Unix timestamp first
  const numericTimestamp = parseInt(timeParam, 10);
  if (!isNaN(numericTimestamp) && numericTimestamp.toString() === timeParam) {
    // String is a valid integer - treat as Unix seconds
    logger.debug({ timeParam, parsedTimestamp: numericTimestamp }, 'Parsed string as Unix timestamp');
    return numericTimestamp;
  }

  // Parse date string using chrono
  try {
    const parsed = chrono.parseDate(timeParam);
    if (!parsed) {
      logger.warn({ timeParam }, 'Failed to parse time string');
      return null;
    }

    // Convert to Unix seconds
    return Math.floor(parsed.getTime() / 1000);
  } catch (error) {
    logger.error({ error, timeParam }, 'Error parsing time string');
    return null;
  }
}
