/**
 * API Usage Tracker for BaoLife
 * Tracks API usage and costs for OpenAI calls.
 * Provides insights into spending patterns and enables budget enforcement.
 * Ported from Python api_usage_tracker.py
 */

import { getConnection } from '../database/pool.js';

// ============================================================================
// Types
// ============================================================================

export interface APIUsage {
  prompt_tokens: number;
  completion_tokens: number;
  total_tokens: number;
}

export interface PlayerUsageStats {
  total_cost: number;
  total_tokens: number;
  total_calls: number;
  avg_cost_per_call: number;
  first_call: Date | null;
  last_call: Date | null;
  period_days: number;
}

export interface UsageByPurpose {
  purpose: string;
  call_count: number;
  total_tokens: number;
  total_cost: number;
  avg_cost: number;
}

export interface TotalUsageStats {
  unique_players: number;
  total_cost: number;
  total_tokens: number;
  total_calls: number;
  avg_cost_per_call: number;
  period_days: number;
}

export interface BudgetStatus {
  total_cost: number;
  monthly_limit: number;
  remaining: number;
  percentage_used: number;
  over_budget: boolean;
  warning: boolean;
}

// ============================================================================
// Pricing
// ============================================================================

// Model pricing (as of March 2026)
export const API_PRICING: Record<string, { input: number; output: number }> = {
  // OpenAI
  'gpt-4o-mini': { input: 0.15 / 1_000_000, output: 0.60 / 1_000_000 },
  'gpt-4o': { input: 2.50 / 1_000_000, output: 10.00 / 1_000_000 },
  // Together AI - Qwen
  'Qwen/Qwen3-Next-80B-A3B-Instruct': { input: 0.15 / 1_000_000, output: 1.50 / 1_000_000 },
  'Qwen/Qwen3-235B-A22B-Instruct-2507': { input: 0.20 / 1_000_000, output: 0.60 / 1_000_000 },
  'Qwen/Qwen3-235B-A22B-Instruct-2507-tput': { input: 0.20 / 1_000_000, output: 0.60 / 1_000_000 },
  'Qwen/Qwen3-235B-A22B-fp8-tput': { input: 0.20 / 1_000_000, output: 0.60 / 1_000_000 },
  'Qwen/Qwen3.5-397B-A17B': { input: 0.60 / 1_000_000, output: 3.60 / 1_000_000 },
  'Qwen/Qwen2.5-72B-Instruct-Turbo': { input: 0.60 / 1_000_000, output: 0.60 / 1_000_000 },
  // Together AI - OpenAI open-source
  'openai/gpt-oss-120b': { input: 0.15 / 1_000_000, output: 0.60 / 1_000_000 },
  'openai/gpt-oss-20b': { input: 0.10 / 1_000_000, output: 0.30 / 1_000_000 },
  // Together AI - Meta Llama
  'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8': { input: 0.27 / 1_000_000, output: 0.85 / 1_000_000 },
  'meta-llama/Llama-4-Scout-17B-16E-Instruct': { input: 0.18 / 1_000_000, output: 0.59 / 1_000_000 },
  'meta-llama/Llama-3.3-70B-Instruct-Turbo': { input: 0.88 / 1_000_000, output: 0.88 / 1_000_000 },
  // Together AI - MiniMax
  'MiniMaxAI/MiniMax-M2.5': { input: 0.30 / 1_000_000, output: 1.20 / 1_000_000 },
  // Together AI - DeepSeek
  'deepseek-ai/DeepSeek-V3': { input: 0.60 / 1_000_000, output: 1.70 / 1_000_000 },
  'deepseek-ai/DeepSeek-R1': { input: 0.60 / 1_000_000, output: 1.70 / 1_000_000 },
  // Together AI / Mistral Direct
  'mistralai/Mistral-Small-24B-Instruct-2501': { input: 0.10 / 1_000_000, output: 0.30 / 1_000_000 },
  'mistral-small-latest': { input: 0.10 / 1_000_000, output: 0.30 / 1_000_000 },
  'mistralai/mistral-small-3.1-24b-instruct': { input: 0.10 / 1_000_000, output: 0.30 / 1_000_000 },
};

// ============================================================================
// API Usage Tracker
// ============================================================================

export class APIUsageTracker {
  private tableCreated = false;

  /**
   * Create api_usage table if it doesn't exist
   */
  async createTableIfNotExists(): Promise<void> {
    let connection;
    try {
      connection = await getConnection();
      await connection.execute(`
        CREATE TABLE IF NOT EXISTS api_usage (
          id INT AUTO_INCREMENT PRIMARY KEY,
          player_id VARCHAR(255) NOT NULL,
          conversation_id VARCHAR(255),
          endpoint VARCHAR(50) NOT NULL,
          model VARCHAR(100) NOT NULL,
          prompt_tokens INT NOT NULL,
          completion_tokens INT NOT NULL,
          total_tokens INT NOT NULL,
          cost_usd DECIMAL(10, 6) NOT NULL,
          created_date DATETIME NOT NULL,
          purpose VARCHAR(100),
          INDEX idx_player (player_id),
          INDEX idx_created (created_date),
          INDEX idx_conversation (conversation_id),
          INDEX idx_model (model)
        )
      `);
      await connection.execute(`
        ALTER TABLE api_usage
          MODIFY COLUMN player_id VARCHAR(255) NOT NULL,
          MODIFY COLUMN conversation_id VARCHAR(255) NULL,
          MODIFY COLUMN model VARCHAR(100) NOT NULL
      `);
      console.log('API usage table created/verified');
    } catch (error) {
      console.error('Failed to create api_usage table:', error);
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }

  /**
   * Calculate cost for API usage
   */
  calculateCost(model: string, usage: APIUsage): number {
    const pricing = API_PRICING[model] || API_PRICING['gpt-4o-mini'];

    if (!API_PRICING[model]) {
      console.warn(`Unknown model '${model}', using gpt-4o-mini pricing`);
    }

    return (
      usage.prompt_tokens * pricing.input +
      usage.completion_tokens * pricing.output
    );
  }

  /**
   * Log API usage to database
   */
  async trackUsage(
    playerId: string,
    conversationId: string | null,
    model: string,
    usage: APIUsage,
    purpose = 'conversation'
  ): Promise<number> {
    // Lazy table creation on first use
    if (!this.tableCreated) {
      await this.createTableIfNotExists();
      this.tableCreated = true;
    }

    const cost = this.calculateCost(model, usage);

    let connection;
    try {
      connection = await getConnection();
      await connection.execute(
        `INSERT INTO api_usage
         (player_id, conversation_id, endpoint, model,
          prompt_tokens, completion_tokens, total_tokens, cost_usd,
          created_date, purpose)
         VALUES (?, ?, 'openai_chat', ?, ?, ?, ?, ?, NOW(), ?)`,
        [
          playerId,
          conversationId,
          model,
          usage.prompt_tokens,
          usage.completion_tokens,
          usage.total_tokens,
          cost,
          purpose,
        ]
      );
    } catch (error) {
      console.error('Failed to track API usage:', error);
    } finally {
      if (connection) {
        connection.release();
      }
    }

    return cost;
  }

  /**
   * Get player's API usage statistics for last N days
   */
  async getPlayerUsage(playerId: string, days = 30): Promise<PlayerUsageStats | null> {
    let connection;
    try {
      connection = await getConnection();
      const [rows] = await connection.execute(
        `SELECT
           SUM(cost_usd) as total_cost,
           SUM(total_tokens) as total_tokens,
           COUNT(*) as total_calls,
           AVG(cost_usd) as avg_cost_per_call,
           MIN(created_date) as first_call,
           MAX(created_date) as last_call
         FROM api_usage
         WHERE player_id = ?
         AND created_date >= DATE_SUB(NOW(), INTERVAL ? DAY)`,
        [playerId, days]
      );

      const results = rows as Array<{
        total_cost: string | null;
        total_tokens: string | null;
        total_calls: number;
        avg_cost_per_call: string | null;
        first_call: Date | null;
        last_call: Date | null;
      }>;

      if (results.length && results[0].total_calls > 0) {
        const result = results[0];
        return {
          total_cost: parseFloat(result.total_cost || '0'),
          total_tokens: parseInt(result.total_tokens || '0', 10),
          total_calls: result.total_calls,
          avg_cost_per_call: parseFloat(result.avg_cost_per_call || '0'),
          first_call: result.first_call,
          last_call: result.last_call,
          period_days: days,
        };
      }

      return {
        total_cost: 0,
        total_tokens: 0,
        total_calls: 0,
        avg_cost_per_call: 0,
        first_call: null,
        last_call: null,
        period_days: days,
      };
    } catch (error) {
      console.error('Failed to get player usage:', error);
      return null;
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }

  /**
   * Get usage breakdown by purpose
   */
  async getUsageByPurpose(playerId?: string, days = 30): Promise<UsageByPurpose[]> {
    let connection;
    try {
      connection = await getConnection();

      let query: string;
      let params: (string | number)[];

      if (playerId) {
        query = `
          SELECT
            purpose,
            COUNT(*) as call_count,
            SUM(total_tokens) as total_tokens,
            SUM(cost_usd) as total_cost,
            AVG(cost_usd) as avg_cost
          FROM api_usage
          WHERE player_id = ?
          AND created_date >= DATE_SUB(NOW(), INTERVAL ? DAY)
          GROUP BY purpose
          ORDER BY total_cost DESC
        `;
        params = [playerId, days];
      } else {
        query = `
          SELECT
            purpose,
            COUNT(*) as call_count,
            SUM(total_tokens) as total_tokens,
            SUM(cost_usd) as total_cost,
            AVG(cost_usd) as avg_cost
          FROM api_usage
          WHERE created_date >= DATE_SUB(NOW(), INTERVAL ? DAY)
          GROUP BY purpose
          ORDER BY total_cost DESC
        `;
        params = [days];
      }

      const [rows] = await connection.execute(query, params);
      const results = rows as Array<{
        purpose: string;
        call_count: number;
        total_tokens: string;
        total_cost: string;
        avg_cost: string;
      }>;

      return results.map((row) => ({
        purpose: row.purpose,
        call_count: row.call_count,
        total_tokens: parseInt(row.total_tokens, 10),
        total_cost: parseFloat(row.total_cost),
        avg_cost: parseFloat(row.avg_cost),
      }));
    } catch (error) {
      console.error('Failed to get usage by purpose:', error);
      return [];
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }

  /**
   * Get total usage across all players
   */
  async getTotalUsage(days = 30): Promise<TotalUsageStats | null> {
    let connection;
    try {
      connection = await getConnection();
      const [rows] = await connection.execute(
        `SELECT
           COUNT(DISTINCT player_id) as unique_players,
           SUM(cost_usd) as total_cost,
           SUM(total_tokens) as total_tokens,
           COUNT(*) as total_calls,
           AVG(cost_usd) as avg_cost_per_call
         FROM api_usage
         WHERE created_date >= DATE_SUB(NOW(), INTERVAL ? DAY)`,
        [days]
      );

      const results = rows as Array<{
        unique_players: number;
        total_cost: string | null;
        total_tokens: string | null;
        total_calls: number;
        avg_cost_per_call: string | null;
      }>;

      if (results.length) {
        const result = results[0];
        return {
          unique_players: result.unique_players || 0,
          total_cost: parseFloat(result.total_cost || '0'),
          total_tokens: parseInt(result.total_tokens || '0', 10),
          total_calls: result.total_calls || 0,
          avg_cost_per_call: parseFloat(result.avg_cost_per_call || '0'),
          period_days: days,
        };
      }

      return null;
    } catch (error) {
      console.error('Failed to get total usage:', error);
      return null;
    } finally {
      if (connection) {
        connection.release();
      }
    }
  }

  /**
   * Check if player is within budget
   */
  async checkPlayerBudget(
    playerId: string,
    monthlyLimit = 5.0
  ): Promise<BudgetStatus | null> {
    const usage = await this.getPlayerUsage(playerId, 30);

    if (usage) {
      const totalCost = usage.total_cost;
      const remaining = monthlyLimit - totalCost;
      const percentageUsed =
        monthlyLimit > 0 ? (totalCost / monthlyLimit) * 100 : 0;

      return {
        total_cost: totalCost,
        monthly_limit: monthlyLimit,
        remaining,
        percentage_used: percentageUsed,
        over_budget: totalCost > monthlyLimit,
        warning: percentageUsed > 80, // Warning at 80%
      };
    }

    return null;
  }

  /**
   * Print usage report to console
   */
  async printUsageReport(playerId?: string, days = 7): Promise<void> {
    console.log('\n' + '='.repeat(60));

    let usage: PlayerUsageStats | TotalUsageStats | null;

    if (playerId) {
      console.log(`API Usage Report for Player ${playerId} (Last ${days} days)`);
      usage = await this.getPlayerUsage(playerId, days);
    } else {
      console.log(`Total API Usage Report (Last ${days} days)`);
      usage = await this.getTotalUsage(days);
    }

    if (usage) {
      console.log('='.repeat(60));
      console.log(`Total Cost:      $${usage.total_cost.toFixed(4)}`);
      console.log(`Total Tokens:    ${usage.total_tokens.toLocaleString()}`);
      console.log(`Total Calls:     ${usage.total_calls.toLocaleString()}`);
      console.log(`Avg Cost/Call:   $${usage.avg_cost_per_call.toFixed(4)}`);

      if (!playerId && 'unique_players' in usage) {
        console.log(`Unique Players:  ${usage.unique_players}`);
      }

      // Show breakdown by purpose
      console.log('\n' + '-'.repeat(60));
      console.log('Usage by Purpose:');
      const byPurpose = await this.getUsageByPurpose(playerId, days);
      for (const item of byPurpose) {
        console.log(
          `  ${item.purpose.padEnd(20)} | Calls: ${item.call_count
            .toString()
            .padStart(4)} | ` +
            `Cost: $${item.total_cost.toFixed(4).padStart(6)} | Avg: $${item.avg_cost
              .toFixed(4)
              .padStart(6)}`
        );
      }
    }

    console.log('='.repeat(60) + '\n');
  }
}

// ============================================================================
// Singleton and Export
// ============================================================================

export const apiUsageTracker = new APIUsageTracker();

export const tracker = {
  APIUsageTracker,
  apiUsageTracker,
  API_PRICING,
};
