/**
 * Analytics Manager
 *
 * Core analytics tracking system for player actions and events.
 * Supports event batching, session tracking, and database persistence.
 *
 * Ported from Python: ws/analytics/events.py
 */

import { v4 as uuidv4 } from 'uuid';
import type { RowDataPacket } from 'mysql2/promise';
import { execute, query, getConnection } from '../../database/index.js';
import { getJobManager } from '../background/jobs.js';
import {
  AnalyticsEvent,
  AnalyticsSession,
  EventCategory,
  EventProperties,
  EventName,
  EventBufferConfig,
  DEFAULT_BUFFER_CONFIG,
  EVENT_NAMES,
  getEventCategory,
  validateEvent,
  PurchaseEventProperties,
  SocialEventProperties,
  ProgressionEventProperties,
  TutorialEventProperties,
  RetentionEventProperties,
  GameplayEventProperties,
} from './events.js';

// ============================================================================
// Analytics Tracker Class
// ============================================================================

/**
 * Main analytics tracking class.
 * Handles event tracking, session management, and batched persistence.
 */
export class AnalyticsTracker {
  private sessions: Map<string, string> = new Map(); // playerId -> sessionId
  private eventBuffer: AnalyticsEvent[] = [];
  private config: EventBufferConfig;
  private flushTimer: NodeJS.Timeout | null = null;
  private initialized = false;

  constructor(config: Partial<EventBufferConfig> = {}) {
    this.config = { ...DEFAULT_BUFFER_CONFIG, ...config };
  }

  // ==========================================================================
  // Initialization
  // ==========================================================================

  /**
   * Initialize the analytics system, creating necessary tables.
   */
  async initialize(): Promise<void> {
    if (this.initialized) return;

    try {
      // Create analytics_events table
      await execute(`
        CREATE TABLE IF NOT EXISTS analytics_events (
          id VARCHAR(36) PRIMARY KEY,
          player_id VARCHAR(255) NOT NULL,
          session_id VARCHAR(36),
          event_name VARCHAR(100) NOT NULL,
          category VARCHAR(50) NOT NULL,
          properties JSON,
          timestamp BIGINT NOT NULL,
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          INDEX idx_player_id (player_id),
          INDEX idx_event_name (event_name),
          INDEX idx_category (category),
          INDEX idx_timestamp (timestamp),
          INDEX idx_session_id (session_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
      `);

      // Create analytics_sessions table
      await execute(`
        CREATE TABLE IF NOT EXISTS analytics_sessions (
          session_id VARCHAR(36) PRIMARY KEY,
          player_id VARCHAR(255) NOT NULL,
          started_at BIGINT NOT NULL,
          ended_at BIGINT,
          duration_seconds INT,
          events_count INT DEFAULT 0,
          platform VARCHAR(50),
          app_version VARCHAR(20),
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          INDEX idx_player_id (player_id),
          INDEX idx_started_at (started_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
      `);

      // Start auto-flush timer
      this.startFlushTimer();

      this.initialized = true;
      console.log('[Analytics] Initialized analytics tracking system');
    } catch (error) {
      console.error('[Analytics] Failed to initialize:', error);
      throw error;
    }
  }

  /**
   * Register analytics flush job with background job manager.
   */
  registerFlushJob(): void {
    const jobManager = getJobManager();
    jobManager.register(
      'analytics_flush',
      this.config.flushIntervalMs,
      async () => {
        await this.flush();
      },
      false
    );
    console.log('[Analytics] Registered analytics flush job');
  }

  // ==========================================================================
  // Session Management
  // ==========================================================================

  /**
   * Start a new analytics session for a player.
   */
  async startSession(
    playerId: string,
    platform?: string,
    appVersion?: string
  ): Promise<string> {
    const sessionId = uuidv4();
    const timestamp = Math.floor(Date.now() / 1000);

    this.sessions.set(playerId, sessionId);

    try {
      await execute(
        `INSERT INTO analytics_sessions (session_id, player_id, started_at, platform, app_version)
         VALUES (?, ?, ?, ?, ?)`,
        [sessionId, playerId, timestamp, platform || null, appVersion || null]
      );
    } catch (error) {
      console.error(`[Analytics] Failed to start session for player ${playerId}:`, error);
    }

    // Track session start event
    this.trackEvent(playerId, EVENT_NAMES.SESSION_START, {
      platform,
      appVersion,
    });

    console.log(`[Analytics] Started session ${sessionId} for player ${playerId}`);
    return sessionId;
  }

  /**
   * End the current session for a player.
   */
  async endSession(playerId: string): Promise<void> {
    const sessionId = this.sessions.get(playerId);
    if (!sessionId) return;

    const endedAt = Math.floor(Date.now() / 1000);

    try {
      await execute(
        `UPDATE analytics_sessions
         SET ended_at = ?,
             duration_seconds = ? - started_at
         WHERE session_id = ?`,
        [endedAt, endedAt, sessionId]
      );
    } catch (error) {
      console.error(`[Analytics] Failed to end session for player ${playerId}:`, error);
    }

    // Track session end event
    this.trackEvent(playerId, EVENT_NAMES.SESSION_END);

    this.sessions.delete(playerId);
    console.log(`[Analytics] Ended session ${sessionId} for player ${playerId}`);
  }

  /**
   * Get the current session ID for a player.
   */
  getSessionId(playerId: string): string | null {
    return this.sessions.get(playerId) || null;
  }

  // ==========================================================================
  // Event Tracking
  // ==========================================================================

  /**
   * Track an analytics event.
   */
  trackEvent(
    playerId: string,
    eventName: EventName | string,
    properties: EventProperties | null = null
  ): void {
    const event: AnalyticsEvent = {
      id: uuidv4(),
      playerId,
      sessionId: this.sessions.get(playerId) || null,
      eventName,
      category: getEventCategory(eventName),
      properties,
      timestamp: Math.floor(Date.now() / 1000),
    };

    this.eventBuffer.push(event);

    // Auto-flush if buffer is full
    if (this.eventBuffer.length >= this.config.maxSize) {
      this.flush().catch((error) =>
        console.error('[Analytics] Auto-flush failed:', error)
      );
    }
  }

  // ==========================================================================
  // Convenience Tracking Methods
  // ==========================================================================

  /**
   * Track purchase initiated event.
   */
  trackPurchaseInitiated(
    playerId: string,
    itemId: string,
    price: number,
    currency = 'USD'
  ): void {
    const properties: PurchaseEventProperties = {
      itemId,
      price,
      currency,
    };
    this.trackEvent(playerId, EVENT_NAMES.PURCHASE_INITIATED, properties);
  }

  /**
   * Track purchase completed event.
   */
  trackPurchaseCompleted(
    playerId: string,
    itemId: string,
    price: number,
    currency = 'USD',
    transactionId?: string
  ): void {
    const properties: PurchaseEventProperties = {
      itemId,
      price,
      currency,
      transactionId,
    };
    this.trackEvent(playerId, EVENT_NAMES.PURCHASE_COMPLETED, properties);
  }

  /**
   * Track conversation sent event.
   */
  trackConversationSent(
    playerId: string,
    npcId: string,
    messageType: string
  ): void {
    const properties: SocialEventProperties = {
      npcId,
      messageType,
    };
    this.trackEvent(playerId, EVENT_NAMES.CONVERSATION_SENT, properties);
  }

  /**
   * Track level up event.
   */
  trackLevelUp(playerId: string, newLevel: number, category = 'general'): void {
    const properties: ProgressionEventProperties = {
      level: newLevel,
      category,
    };
    this.trackEvent(playerId, EVENT_NAMES.LEVEL_UP, properties);
  }

  /**
   * Track tutorial step event.
   */
  trackTutorialStep(
    playerId: string,
    stepName: string,
    completed = true
  ): void {
    const properties: TutorialEventProperties = {
      stepName,
      completed,
    };
    this.trackEvent(playerId, EVENT_NAMES.TUTORIAL_STEP, properties);
  }

  /**
   * Track relationship milestone event.
   */
  trackRelationshipMilestone(
    playerId: string,
    npcId: string,
    relationshipType: string,
    affinityLevel: number
  ): void {
    const properties: SocialEventProperties = {
      npcId,
      relationshipType,
      affinityLevel,
    };
    this.trackEvent(playerId, EVENT_NAMES.MILESTONE_REACHED, properties);
  }

  /**
   * Track character death event.
   */
  trackDeath(playerId: string, age: number, cause: string): void {
    const properties: GameplayEventProperties = {
      characterAge: age,
      outcome: cause,
    };
    this.trackEvent(playerId, EVENT_NAMES.CHARACTER_DEATH, properties);
  }

  /**
   * Track achievement unlocked event.
   */
  trackAchievementUnlocked(
    playerId: string,
    achievementKey: string,
    category: string
  ): void {
    const properties: ProgressionEventProperties = {
      achievementKey,
      category,
    };
    this.trackEvent(playerId, EVENT_NAMES.ACHIEVEMENT_UNLOCKED, properties);
  }

  /**
   * Track daily login event.
   */
  trackDailyLogin(playerId: string, streakDays: number): void {
    const properties: RetentionEventProperties = {
      streakDays,
    };
    this.trackEvent(playerId, EVENT_NAMES.DAILY_LOGIN, properties);
  }

  /**
   * Track daily quest completed event.
   */
  trackDailyQuestCompleted(
    playerId: string,
    questType: string,
    rewardType: string,
    rewardAmount: number
  ): void {
    const properties: RetentionEventProperties = {
      questType,
      rewardType,
      rewardAmount,
    };
    this.trackEvent(playerId, EVENT_NAMES.DAILY_QUEST_COMPLETED, properties);
  }

  /**
   * Track money earned event.
   */
  trackMoneyEarned(playerId: string, amount: number, source: string): void {
    const properties: ProgressionEventProperties = {
      amount,
      category: source,
    };
    this.trackEvent(playerId, EVENT_NAMES.MONEY_EARNED, properties);
  }

  /**
   * Track money spent event.
   */
  trackMoneySpent(playerId: string, amount: number, category: string): void {
    const properties: ProgressionEventProperties = {
      amount,
      category,
    };
    this.trackEvent(playerId, EVENT_NAMES.MONEY_SPENT, properties);
  }

  /**
   * Track job obtained event.
   */
  trackJobObtained(playerId: string, jobId: string, jobTitle: string): void {
    const properties: ProgressionEventProperties = {
      jobId,
      jobTitle,
    };
    this.trackEvent(playerId, EVENT_NAMES.JOB_OBTAINED, properties);
  }

  /**
   * Track game start event.
   */
  trackGameStart(playerId: string, characterAge?: number): void {
    const properties: GameplayEventProperties = {
      characterAge,
    };
    this.trackEvent(playerId, EVENT_NAMES.GAME_START, properties);
  }

  // ==========================================================================
  // Buffer Management
  // ==========================================================================

  /**
   * Flush buffered events to database.
   */
  async flush(): Promise<void> {
    if (this.eventBuffer.length === 0) return;

    const eventsToFlush = [...this.eventBuffer];
    this.eventBuffer = [];

    try {
      const connection = await getConnection();
      try {
        await connection.beginTransaction();

        // Insert events in batch
        const insertQuery = `
          INSERT INTO analytics_events
            (id, player_id, session_id, event_name, category, properties, timestamp)
          VALUES (?, ?, ?, ?, ?, ?, ?)
        `;

        for (const event of eventsToFlush) {
          await connection.execute(insertQuery, [
            event.id,
            event.playerId,
            event.sessionId,
            event.eventName,
            event.category,
            event.properties ? JSON.stringify(event.properties) : null,
            event.timestamp,
          ]);
        }

        // Update session event counts
        const sessionUpdates = new Map<string, number>();
        for (const event of eventsToFlush) {
          if (event.sessionId) {
            sessionUpdates.set(
              event.sessionId,
              (sessionUpdates.get(event.sessionId) || 0) + 1
            );
          }
        }

        for (const [sessionId, count] of sessionUpdates) {
          await connection.execute(
            `UPDATE analytics_sessions
             SET events_count = events_count + ?
             WHERE session_id = ?`,
            [count, sessionId]
          );
        }

        await connection.commit();
        console.log(`[Analytics] Flushed ${eventsToFlush.length} events to database`);
      } catch (error) {
        await connection.rollback();
        throw error;
      } finally {
        connection.release();
      }
    } catch (error) {
      console.error('[Analytics] Failed to flush events:', error);
      // Put events back in buffer for retry
      this.eventBuffer.unshift(...eventsToFlush);
    }
  }

  /**
   * Start the auto-flush timer.
   */
  private startFlushTimer(): void {
    if (this.flushTimer) return;

    this.flushTimer = setInterval(() => {
      this.flush().catch((error) =>
        console.error('[Analytics] Scheduled flush failed:', error)
      );
    }, this.config.flushIntervalMs);
  }

  /**
   * Stop the auto-flush timer.
   */
  stopFlushTimer(): void {
    if (this.flushTimer) {
      clearInterval(this.flushTimer);
      this.flushTimer = null;
    }
  }

  /**
   * Get buffer stats.
   */
  getBufferStats(): { size: number; maxSize: number } {
    return {
      size: this.eventBuffer.length,
      maxSize: this.config.maxSize,
    };
  }

  // ==========================================================================
  // Query Methods
  // ==========================================================================

  /**
   * Get events for a player.
   */
  async getPlayerEvents(
    playerId: string,
    options: {
      limit?: number;
      offset?: number;
      eventName?: string;
      category?: EventCategory;
      startTime?: number;
      endTime?: number;
    } = {}
  ): Promise<AnalyticsEvent[]> {
    const { limit = 100, offset = 0, eventName, category, startTime, endTime } = options;

    let sql = `
      SELECT id, player_id, session_id, event_name, category, properties, timestamp, created_at
      FROM analytics_events
      WHERE player_id = ?
    `;
    const params: (string | number)[] = [playerId];

    if (eventName) {
      sql += ' AND event_name = ?';
      params.push(eventName);
    }

    if (category) {
      sql += ' AND category = ?';
      params.push(category);
    }

    if (startTime) {
      sql += ' AND timestamp >= ?';
      params.push(startTime);
    }

    if (endTime) {
      sql += ' AND timestamp <= ?';
      params.push(endTime);
    }

    sql += ' ORDER BY timestamp DESC LIMIT ? OFFSET ?';
    params.push(limit, offset);

    interface EventRow extends RowDataPacket {
      id: string;
      player_id: string;
      session_id: string | null;
      event_name: string;
      category: string;
      properties: string | null;
      timestamp: number;
      created_at: Date;
    }

    const rows = await query<EventRow[]>(sql, params);

    return rows.map((row) => ({
      id: row.id,
      playerId: row.player_id,
      sessionId: row.session_id,
      eventName: row.event_name,
      category: row.category as EventCategory,
      properties: row.properties ? JSON.parse(row.properties) : null,
      timestamp: row.timestamp,
      createdAt: row.created_at,
    }));
  }

  /**
   * Get event count for a player.
   */
  async getEventCount(
    playerId: string,
    eventName?: string,
    startTime?: number,
    endTime?: number
  ): Promise<number> {
    let sql = 'SELECT COUNT(*) as count FROM analytics_events WHERE player_id = ?';
    const params: (string | number)[] = [playerId];

    if (eventName) {
      sql += ' AND event_name = ?';
      params.push(eventName);
    }

    if (startTime) {
      sql += ' AND timestamp >= ?';
      params.push(startTime);
    }

    if (endTime) {
      sql += ' AND timestamp <= ?';
      params.push(endTime);
    }

    interface CountRow extends RowDataPacket {
      count: number;
    }

    const [row] = await query<CountRow[]>(sql, params);
    return row?.count || 0;
  }

  /**
   * Get player sessions.
   */
  async getPlayerSessions(
    playerId: string,
    limit = 10
  ): Promise<AnalyticsSession[]> {
    interface SessionRow extends RowDataPacket {
      session_id: string;
      player_id: string;
      started_at: number;
      ended_at: number | null;
      duration_seconds: number | null;
      events_count: number;
      platform: string | null;
      app_version: string | null;
    }

    const rows = await query<SessionRow[]>(
      `SELECT session_id, player_id, started_at, ended_at, duration_seconds,
              events_count, platform, app_version
       FROM analytics_sessions
       WHERE player_id = ?
       ORDER BY started_at DESC
       LIMIT ?`,
      [playerId, limit]
    );

    return rows.map((row) => ({
      sessionId: row.session_id,
      playerId: row.player_id,
      startedAt: row.started_at,
      endedAt: row.ended_at,
      durationSeconds: row.duration_seconds,
      eventsCount: row.events_count,
      platform: row.platform || undefined,
      appVersion: row.app_version || undefined,
    }));
  }

  /**
   * Get aggregate metrics for a player.
   */
  async getPlayerMetrics(playerId: string): Promise<{
    totalSessions: number;
    totalEvents: number;
    totalPlayTime: number; // in seconds
    firstSeen: number | null;
    lastSeen: number | null;
    eventsByCategory: Record<EventCategory, number>;
  }> {
    // Get session stats
    interface SessionStats extends RowDataPacket {
      total_sessions: number;
      total_play_time: number;
      first_seen: number;
      last_seen: number;
    }

    const [sessionStats] = await query<SessionStats[]>(
      `SELECT
         COUNT(*) as total_sessions,
         COALESCE(SUM(duration_seconds), 0) as total_play_time,
         MIN(started_at) as first_seen,
         MAX(COALESCE(ended_at, started_at)) as last_seen
       FROM analytics_sessions
       WHERE player_id = ?`,
      [playerId]
    );

    // Get event counts by category
    interface CategoryCount extends RowDataPacket {
      category: EventCategory;
      count: number;
    }

    const categoryCounts = await query<CategoryCount[]>(
      `SELECT category, COUNT(*) as count
       FROM analytics_events
       WHERE player_id = ?
       GROUP BY category`,
      [playerId]
    );

    const eventsByCategory: Record<EventCategory, number> = {
      session: 0,
      purchase: 0,
      gameplay: 0,
      social: 0,
      progression: 0,
      tutorial: 0,
      retention: 0,
      system: 0,
    };

    let totalEvents = 0;
    for (const row of categoryCounts) {
      eventsByCategory[row.category] = row.count;
      totalEvents += row.count;
    }

    return {
      totalSessions: sessionStats?.total_sessions || 0,
      totalEvents,
      totalPlayTime: sessionStats?.total_play_time || 0,
      firstSeen: sessionStats?.first_seen || null,
      lastSeen: sessionStats?.last_seen || null,
      eventsByCategory,
    };
  }

  // ==========================================================================
  // Cleanup
  // ==========================================================================

  /**
   * Delete old analytics data.
   */
  async cleanupOldData(daysToKeep = 90): Promise<number> {
    const cutoffTimestamp = Math.floor(Date.now() / 1000) - daysToKeep * 24 * 60 * 60;

    interface DeleteResult {
      affectedRows: number;
    }

    // Delete old events
    const result = await execute(
      'DELETE FROM analytics_events WHERE timestamp < ?',
      [cutoffTimestamp]
    ) as DeleteResult;

    // Delete old sessions
    await execute(
      'DELETE FROM analytics_sessions WHERE started_at < ? AND session_id NOT IN (SELECT DISTINCT session_id FROM analytics_events WHERE session_id IS NOT NULL)',
      [cutoffTimestamp]
    );

    console.log(`[Analytics] Cleaned up events older than ${daysToKeep} days`);
    return result.affectedRows || 0;
  }

  /**
   * Clear all data for a player.
   */
  async clearPlayerData(playerId: string): Promise<void> {
    await execute('DELETE FROM analytics_events WHERE player_id = ?', [playerId]);
    await execute('DELETE FROM analytics_sessions WHERE player_id = ?', [playerId]);
    this.sessions.delete(playerId);
    console.log(`[Analytics] Cleared all data for player ${playerId}`);
  }

  /**
   * Shutdown the tracker gracefully.
   */
  async shutdown(): Promise<void> {
    this.stopFlushTimer();
    await this.flush();
    console.log('[Analytics] Tracker shut down');
  }
}

// ============================================================================
// Global Instance
// ============================================================================

let tracker: AnalyticsTracker | null = null;

/**
 * Get or create the global analytics tracker instance.
 */
export function getTracker(config?: Partial<EventBufferConfig>): AnalyticsTracker {
  if (!tracker) {
    tracker = new AnalyticsTracker(config);
  }
  return tracker;
}

/**
 * Initialize the global tracker.
 */
export async function initializeAnalytics(
  config?: Partial<EventBufferConfig>
): Promise<AnalyticsTracker> {
  const t = getTracker(config);
  await t.initialize();
  return t;
}

// ============================================================================
// Convenience Functions (mirror Python API)
// ============================================================================

/**
 * Track an event (convenience function).
 */
export function trackEvent(
  playerId: string,
  eventName: EventName | string,
  properties: EventProperties | null = null
): void {
  getTracker().trackEvent(playerId, eventName, properties);
}

/**
 * Track purchase initiated (convenience function).
 */
export function trackPurchaseInitiated(
  playerId: string,
  itemId: string,
  price: number,
  currency = 'USD'
): void {
  getTracker().trackPurchaseInitiated(playerId, itemId, price, currency);
}

/**
 * Track purchase completed (convenience function).
 */
export function trackPurchaseCompleted(
  playerId: string,
  itemId: string,
  price: number,
  currency = 'USD',
  transactionId?: string
): void {
  getTracker().trackPurchaseCompleted(playerId, itemId, price, currency, transactionId);
}

/**
 * Track conversation sent (convenience function).
 */
export function trackConversationSent(
  playerId: string,
  npcId: string,
  messageType: string
): void {
  getTracker().trackConversationSent(playerId, npcId, messageType);
}

/**
 * Track level up (convenience function).
 */
export function trackLevelUp(
  playerId: string,
  newLevel: number,
  category = 'general'
): void {
  getTracker().trackLevelUp(playerId, newLevel, category);
}

/**
 * Track tutorial step (convenience function).
 */
export function trackTutorialStep(
  playerId: string,
  stepName: string,
  completed = true
): void {
  getTracker().trackTutorialStep(playerId, stepName, completed);
}

/**
 * Track relationship milestone (convenience function).
 */
export function trackRelationshipMilestone(
  playerId: string,
  npcId: string,
  relationshipType: string,
  affinityLevel: number
): void {
  getTracker().trackRelationshipMilestone(playerId, npcId, relationshipType, affinityLevel);
}

/**
 * Track death (convenience function).
 */
export function trackDeath(
  playerId: string,
  age: number,
  cause: string
): void {
  getTracker().trackDeath(playerId, age, cause);
}
