import { resolve } from 'path';
import cron from 'node-cron';
import { createModuleLogger } from '../../utils/index.js';
import { DataStore } from '../data-store/index.js';
import { atomicWriteJSON, readJSON, ensureDir } from '../data-store/file-ops.js';
import { AnalyticsAggregator } from './aggregator.js';
import type { CharacterAnalytics } from '../../types/api.js';

const log = createModuleLogger('analytics');

export class AnalyticsEngine {
  private dataStore: DataStore;
  private aggregator: AnalyticsAggregator;
  private cronTask: cron.ScheduledTask | null = null;

  constructor(dataStore: DataStore) {
    this.dataStore = dataStore;
    this.aggregator = new AnalyticsAggregator();
  }

  /**
   * Get analytics for a character, loading from stored file or calculating fresh.
   */
  async getCharacterAnalytics(
    characterId: string,
    period: 'daily' | 'weekly' | 'monthly',
    date?: string,
  ): Promise<CharacterAnalytics> {
    const dateStr = date || this.getDateString(period);
    const filePath = this.getAnalyticsPath(characterId, period, dateStr);
    const existing = await readJSON<CharacterAnalytics>(filePath);
    if (existing) return existing;

    // Calculate on-demand if not stored
    if (period === 'daily') return this.calculateDailyAnalytics(characterId, dateStr);
    if (period === 'weekly') return this.calculateWeeklyAnalytics(characterId);
    return this.calculateMonthlyAnalytics(characterId);
  }

  /**
   * Calculate and store daily analytics for a character.
   */
  async calculateDailyAnalytics(characterId: string, date?: string): Promise<CharacterAnalytics> {
    const dateStr = date || this.getDateString('daily');
    const conversations = await this.dataStore.listConversations(characterId);
    const transactions = await this.dataStore.getStarsTransactions(characterId);

    // Filter conversations and transactions for the given date
    const dayStart = new Date(`${dateStr}T00:00:00Z`);
    const dayEnd = new Date(`${dateStr}T23:59:59.999Z`);

    const filteredConversations = conversations.map(conv => ({
      ...conv,
      messages: conv.messages.filter(m => {
        const ts = new Date(m.timestamp).getTime();
        return ts >= dayStart.getTime() && ts <= dayEnd.getTime();
      }),
    })).filter(conv => conv.messages.length > 0);

    const filteredTransactions = transactions.filter(tx => {
      const ts = new Date(tx.createdAt).getTime();
      return ts >= dayStart.getTime() && ts <= dayEnd.getTime();
    });

    const analytics: CharacterAnalytics = {
      characterId,
      period: 'daily',
      date: dateStr,
      conversations: this.aggregator.aggregateConversations(filteredConversations),
      sales: this.aggregator.aggregateSales(filteredTransactions, filteredConversations),
      activity: this.aggregator.aggregateActivity(filteredConversations, filteredTransactions),
    };

    await this.storeAnalytics(analytics);
    log.info({ characterId, date: dateStr }, 'Daily analytics calculated');
    return analytics;
  }

  /**
   * Calculate and store weekly analytics for a character.
   */
  async calculateWeeklyAnalytics(characterId: string): Promise<CharacterAnalytics> {
    const now = new Date();
    const weekStart = new Date(now);
    weekStart.setDate(now.getDate() - now.getDay());
    weekStart.setHours(0, 0, 0, 0);
    const weekEnd = new Date(weekStart);
    weekEnd.setDate(weekStart.getDate() + 6);
    weekEnd.setHours(23, 59, 59, 999);

    const year = weekStart.getFullYear();
    const weekNum = this.getWeekNumber(weekStart);
    const dateStr = `${year}-W${String(weekNum).padStart(2, '0')}`;

    const conversations = await this.dataStore.listConversations(characterId);
    const transactions = await this.dataStore.getStarsTransactions(characterId);

    const filteredConversations = conversations.map(conv => ({
      ...conv,
      messages: conv.messages.filter(m => {
        const ts = new Date(m.timestamp).getTime();
        return ts >= weekStart.getTime() && ts <= weekEnd.getTime();
      }),
    })).filter(conv => conv.messages.length > 0);

    const filteredTransactions = transactions.filter(tx => {
      const ts = new Date(tx.createdAt).getTime();
      return ts >= weekStart.getTime() && ts <= weekEnd.getTime();
    });

    const analytics: CharacterAnalytics = {
      characterId,
      period: 'weekly',
      date: dateStr,
      conversations: this.aggregator.aggregateConversations(filteredConversations),
      sales: this.aggregator.aggregateSales(filteredTransactions, filteredConversations),
      activity: this.aggregator.aggregateActivity(filteredConversations, filteredTransactions),
    };

    await this.storeAnalytics(analytics);
    log.info({ characterId, date: dateStr }, 'Weekly analytics calculated');
    return analytics;
  }

  /**
   * Calculate and store monthly analytics for a character.
   */
  async calculateMonthlyAnalytics(characterId: string): Promise<CharacterAnalytics> {
    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth();
    const monthStart = new Date(year, month, 1, 0, 0, 0, 0);
    const monthEnd = new Date(year, month + 1, 0, 23, 59, 59, 999);
    const dateStr = `${year}-${String(month + 1).padStart(2, '0')}`;

    const conversations = await this.dataStore.listConversations(characterId);
    const transactions = await this.dataStore.getStarsTransactions(characterId);

    const filteredConversations = conversations.map(conv => ({
      ...conv,
      messages: conv.messages.filter(m => {
        const ts = new Date(m.timestamp).getTime();
        return ts >= monthStart.getTime() && ts <= monthEnd.getTime();
      }),
    })).filter(conv => conv.messages.length > 0);

    const filteredTransactions = transactions.filter(tx => {
      const ts = new Date(tx.createdAt).getTime();
      return ts >= monthStart.getTime() && ts <= monthEnd.getTime();
    });

    const analytics: CharacterAnalytics = {
      characterId,
      period: 'monthly',
      date: dateStr,
      conversations: this.aggregator.aggregateConversations(filteredConversations),
      sales: this.aggregator.aggregateSales(filteredTransactions, filteredConversations),
      activity: this.aggregator.aggregateActivity(filteredConversations, filteredTransactions),
    };

    await this.storeAnalytics(analytics);
    log.info({ characterId, date: dateStr }, 'Monthly analytics calculated');
    return analytics;
  }

  /**
   * Compare analytics across multiple characters for a given period.
   */
  async compareCharacters(
    characterIds: string[],
    period: 'daily' | 'weekly' | 'monthly',
  ): Promise<CharacterAnalytics[]> {
    const results: CharacterAnalytics[] = [];
    for (const id of characterIds) {
      const analytics = await this.getCharacterAnalytics(id, period);
      results.push(analytics);
    }
    return results;
  }

  /**
   * Get a system-wide overview of all characters.
   */
  async getSystemOverview(): Promise<{
    totalCharacters: number;
    totalMessages: number;
    totalStars: number;
    topCharacter?: string;
  }> {
    const characters = await this.dataStore.listCharacters();
    let totalMessages = 0;
    let totalStars = 0;
    let topCharacter: string | undefined;
    let topStars = 0;

    for (const char of characters) {
      const conversations = await this.dataStore.listConversations(char.id);
      const transactions = await this.dataStore.getStarsTransactions(char.id);

      const charMessages = conversations.reduce((sum, c) => sum + c.messages.length, 0);
      const charStars = transactions
        .filter(tx => tx.status === 'completed')
        .reduce((sum, tx) => sum + tx.starCount, 0);

      totalMessages += charMessages;
      totalStars += charStars;

      if (charStars > topStars) {
        topStars = charStars;
        topCharacter = char.id;
      }
    }

    return {
      totalCharacters: characters.length,
      totalMessages,
      totalStars,
      topCharacter: topStars > 0 ? topCharacter : undefined,
    };
  }

  /**
   * Start periodic analytics calculation using node-cron.
   * Default: daily at midnight.
   */
  startPeriodicCalculation(cronExpression?: string): void {
    if (this.cronTask) {
      this.cronTask.stop();
    }

    const expression = cronExpression || '0 0 * * *'; // default: daily at midnight
    this.cronTask = cron.schedule(expression, async () => {
      log.info('Running periodic analytics calculation');
      try {
        const characters = await this.dataStore.listCharacters();
        for (const char of characters) {
          await this.calculateDailyAnalytics(char.id);
        }
        log.info('Periodic analytics calculation completed');
      } catch (err) {
        log.error({ err }, 'Periodic analytics calculation failed');
      }
    });

    log.info({ cronExpression: expression }, 'Periodic analytics calculation started');
  }

  /**
   * Stop periodic analytics calculation.
   */
  stopPeriodicCalculation(): void {
    if (this.cronTask) {
      this.cronTask.stop();
      this.cronTask = null;
      log.info('Periodic analytics calculation stopped');
    }
  }

  // --- Private helpers ---

  private async storeAnalytics(analytics: CharacterAnalytics): Promise<void> {
    const filePath = this.getAnalyticsPath(analytics.characterId, analytics.period, analytics.date);
    await ensureDir(resolve(filePath, '..'));
    await atomicWriteJSON(filePath, analytics);
  }

  private getAnalyticsPath(characterId: string, period: string, dateStr: string): string {
    const baseDir = (this.dataStore as any).dataDir || 'data';
    return resolve(baseDir, 'analytics', characterId, period, `${dateStr}.json`);
  }

  private getDateString(period: 'daily' | 'weekly' | 'monthly'): string {
    const now = new Date();
    if (period === 'daily') {
      return now.toISOString().split('T')[0];
    }
    if (period === 'weekly') {
      const weekNum = this.getWeekNumber(now);
      return `${now.getFullYear()}-W${String(weekNum).padStart(2, '0')}`;
    }
    return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
  }

  private getWeekNumber(date: Date): number {
    const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
    const dayNum = d.getUTCDay() || 7;
    d.setUTCDate(d.getUTCDate() + 4 - dayNum);
    const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
    return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
  }
}
