/**
 * Gemini Usage Scanner Service
 *
 * Scans Gemini CLI session files (JSON) for token usage data and records it to the database.
 * Gemini sessions are stored in:
 *   {geminiHomeDir}/.gemini/tmp/{projectHash}/chats/session-*.json
 *
 * Key differences from Claude/Codex sessions:
 * - Gemini uses plain JSON files (not JSONL)
 * - Each file is a full session object with a "messages" array
 * - Token data is in messages with type === "gemini": { input, output, cached, thoughts, tool, total }
 * - "thoughts" tokens are bundled with output tokens (same billing rate)
 * - Pro models have two pricing tiers: ≤200k total tokens and >200k total tokens
 *   - The scanner selects the appropriate tier based on tokens.total per message
 * - Deduplication is done via sessionId + message.id
 */

import { promises as fs } from 'fs';
import path from 'path';
import { usageDb, userDb } from '../database/db.js';
import { calculateCost, normalizeModelName } from './pricing.js';
import { getUserPaths } from './user-directories.js';

const GEMINI_PRO_LARGE_THRESHOLD = 200000; // tokens above this use the higher pricing tier

/**
 * Scan all Gemini session files for a user
 * @param {string} userUuid - User UUID
 */
export async function scanUserGeminiSessions(userUuid) {
  const userPaths = getUserPaths(userUuid);
  const tmpDir = path.join(userPaths.geminiHomeDir, '.gemini', 'tmp');
  const scanStatePath = path.join(userPaths.geminiHomeDir, '.gemini-usage-scan-state.json');

  // Check if gemini tmp directory exists
  try {
    await fs.access(tmpDir);
  } catch {
    // No gemini sessions directory yet
    return;
  }

  // Load scan state
  // scannedMessages: { [sessionId]: string[] } (array of processed message IDs)
  let scanState = { lastScanTime: null, scannedMessages: {} };
  try {
    const stateContent = await fs.readFile(scanStatePath, 'utf8');
    scanState = JSON.parse(stateContent);
    if (!scanState.scannedMessages) scanState.scannedMessages = {};
  } catch {
    // File doesn't exist or is invalid, start fresh
  }

  let newRecordsCount = 0;

  // Sessions are organized as tmp/{projectHash}/chats/session-*.json
  let projectDirs;
  try {
    projectDirs = await fs.readdir(tmpDir, { withFileTypes: true });
  } catch {
    return;
  }

  for (const projectDir of projectDirs) {
    if (!projectDir.isDirectory()) continue;
    const chatsDir = path.join(tmpDir, projectDir.name, 'chats');

    let chatFiles;
    try {
      chatFiles = await fs.readdir(chatsDir);
    } catch {
      continue;
    }

    for (const chatFile of chatFiles) {
      if (!chatFile.startsWith('session-') || !chatFile.endsWith('.json')) continue;

      const sessionPath = path.join(chatsDir, chatFile);

      // Get file stats to check if modified
      let stats;
      try {
        stats = await fs.stat(sessionPath);
      } catch {
        continue;
      }
      const lastModified = stats.mtime.toISOString();

      // Use file path as session key for state tracking (since sessionId is inside the file)
      const stateKey = `${projectDir.name}/${chatFile}`;
      const lastScanned = scanState.scannedMessages[stateKey];

      // If file hasn't changed and we have previously scanned it, skip
      if (lastScanned && lastScanned.lastModified === lastModified) {
        continue;
      }

      // Scan the session file
      try {
        const processedIds = new Set(lastScanned?.processedIds || []);
        const scanResult = await scanGeminiSessionFile(
          userUuid,
          sessionPath,
          processedIds
        );
        newRecordsCount += scanResult.recordsAdded;

        // Update scan state
        scanState.scannedMessages[stateKey] = {
          lastModified,
          processedIds: Array.from(scanResult.processedIds),
          lastScan: new Date().toISOString()
        };
      } catch (error) {
        console.error(`[GeminiUsageScanner] Error scanning session ${chatFile}:`, error.message);
      }
    }
  }

  // Save scan state
  scanState.lastScanTime = new Date().toISOString();
  try {
    await fs.writeFile(scanStatePath, JSON.stringify(scanState, null, 2));
  } catch (error) {
    console.error(`[GeminiUsageScanner] Error saving scan state for user ${userUuid}:`, error.message);
  }

  if (newRecordsCount > 0) {
    console.log(`[GeminiUsageScanner] User ${userUuid}: added ${newRecordsCount} new Gemini usage records`);
  }
}

/**
 * Scan a single Gemini session JSON file for usage data
 * @param {string} userUuid - User UUID
 * @param {string} filePath - Full path to session JSON file
 * @param {Set<string>} processedIds - Already processed message IDs (for deduplication)
 * @returns {{ recordsAdded: number, processedIds: Set<string> }}
 */
async function scanGeminiSessionFile(userUuid, filePath, processedIds) {
  const content = await fs.readFile(filePath, 'utf8');
  let session;
  try {
    session = JSON.parse(content);
  } catch {
    return { recordsAdded: 0, processedIds };
  }

  const sessionId = session.sessionId || path.basename(filePath, '.json');
  const messages = session.messages || [];

  let recordsAdded = 0;

  for (const message of messages) {
    // Only process gemini-type messages that have token data
    if (message.type !== 'gemini' || !message.tokens) continue;

    const messageId = message.id;
    if (!messageId) continue;

    // Skip already processed messages
    if (processedIds.has(messageId)) continue;

    const tokens = message.tokens;
    const inputTokens = tokens.input || 0;
    const cachedTokens = tokens.cached || 0;
    // thoughts tokens are billed at the same rate as output tokens
    const outputTokens = (tokens.output || 0) + (tokens.thoughts || 0);
    const totalTokens = tokens.total || 0;

    // Skip if no meaningful usage
    if (inputTokens === 0 && outputTokens === 0) {
      processedIds.add(messageId);
      continue;
    }

    const rawModel = message.model || 'gemini-3-pro-preview';
    let model = normalizeModelName(rawModel);

    // For pro models, apply the higher pricing tier if total tokens exceed threshold
    // The -large suffix keys are defined in pricing.js exclusively for this purpose
    if (
      totalTokens > GEMINI_PRO_LARGE_THRESHOLD &&
      (model === 'gemini-3.1-pro-preview' || model === 'gemini-3-pro-preview')
    ) {
      model = `${model}-large`;
    }

    const cost = calculateCost({
      model,
      inputTokens,
      outputTokens,
      cacheReadTokens: cachedTokens,
      cacheCreationTokens: 0
    });

    const entryTimestamp = message.timestamp || session.startTime || new Date().toISOString();
    const entryDate = entryTimestamp.split('T')[0];

    // Insert usage record with source='gemini'
    usageDb.insertRecord({
      user_uuid: userUuid,
      session_id: sessionId,
      model,
      raw_model: rawModel,
      input_tokens: inputTokens,
      output_tokens: outputTokens,
      cache_read_tokens: cachedTokens,
      cache_creation_tokens: 0,
      cost_usd: cost,
      source: 'gemini',
      created_at: entryTimestamp
    });

    // Update daily summary
    usageDb.upsertDailySummary({
      user_uuid: userUuid,
      date: entryDate,
      model,
      total_input_tokens: inputTokens,
      total_output_tokens: outputTokens,
      total_cost_usd: cost,
      session_count: 0,
      request_count: 1
    });

    processedIds.add(messageId);
    recordsAdded++;
  }

  return { recordsAdded, processedIds };
}

/**
 * Manually trigger a Gemini scan (for testing or admin use)
 */
export async function triggerGeminiScan() {
  console.log('[GeminiUsageScanner] Manual Gemini scan triggered');
  const users = userDb.getAllUsers();
  for (const user of users) {
    if (!user.uuid) continue;
    try {
      await scanUserGeminiSessions(user.uuid);
    } catch (error) {
      console.error(`[GeminiUsageScanner] Error scanning user ${user.uuid}:`, error.message);
    }
  }
  return { success: true, message: 'Gemini scan completed' };
}
