import type Redis from 'ioredis';
import type { FastifyBaseLogger } from 'fastify';
import type { BaseMessage } from '@langchain/core/messages';
import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
import type { IcebergClient } from '../../clients/iceberg-client.js';

/**
 * Message record for storage
 */
export interface StoredMessage {
  id: string;
  userId: string;
  sessionId: string;
  role: 'user' | 'assistant' | 'system' | 'workspace';
  content: string;
  timestamp: number; // microseconds (Iceberg convention)
  metadata?: Record<string, unknown>;
}

/**
 * Conversation store: Redis (hot) + Iceberg/Parquet (cold)
 *
 * Hot path: Recent messages in Redis for fast context loading
 * Cold path: Full session flushed as a single Parquet file at session end
 *
 * Architecture:
 * - Redis stores last N messages per session with TTL
 * - Parquet file written to S3 at session close (one file per session)
 * - Cold read falls back to Parquet scan when Redis TTL has expired
 */
export class ConversationStore {
  private readonly HOT_MESSAGE_LIMIT = 50; // Redis buffer ceiling
  private readonly HOT_TTL_SECONDS = 86400; // 24 hours

  constructor(
    private redis: Redis,
    private logger: FastifyBaseLogger,
    private icebergClient?: IcebergClient
  ) {}

  /**
   * Save a message to Redis hot path
   */
  async saveMessage(
    userId: string,
    sessionId: string,
    role: 'user' | 'assistant' | 'system' | 'workspace',
    content: string,
    metadata?: Record<string, unknown>,
    channelType?: string
  ): Promise<void> {
    const message: StoredMessage = {
      id: `${userId}:${sessionId}:${Date.now()}`,
      userId,
      sessionId,
      role,
      content,
      timestamp: Date.now() * 1000, // Convert to microseconds
      metadata,
    };

    this.logger.debug({ userId, sessionId, role }, 'Saving message');

    const key = this.getRedisKey(userId, sessionId, channelType);
    await this.redis.lpush(key, JSON.stringify(message));
    await this.redis.ltrim(key, 0, this.HOT_MESSAGE_LIMIT - 1);
    await this.redis.expire(key, this.HOT_TTL_SECONDS);
  }

  /**
   * Get recent messages from Redis (hot path)
   */
  async getRecentMessages(
    userId: string,
    sessionId: string,
    limit: number,
    channelType?: string
  ): Promise<StoredMessage[]> {
    const key = this.getRedisKey(userId, sessionId, channelType);
    const messages = await this.redis.lrange(key, 0, limit - 1);

    return messages
      .map((msg) => {
        try {
          return JSON.parse(msg) as StoredMessage;
        } catch (error) {
          this.logger.error({ error, message: msg }, 'Failed to parse message');
          return null;
        }
      })
      .filter((msg): msg is StoredMessage => msg !== null)
      .reverse(); // Oldest first
  }

  /**
   * Get full conversation history — Redis first, falls back to Iceberg cold path
   */
  async getFullHistory(
    userId: string,
    sessionId: string,
    limit: number,
    channelType?: string,
    timeRange?: { start: number; end: number }
  ): Promise<StoredMessage[]> {
    this.logger.debug({ userId, sessionId }, 'Loading full history');

    // Try Redis hot path first
    const hot = await this.getRecentMessages(userId, sessionId, limit, channelType);
    if (hot.length > 0) {
      return hot;
    }

    // Fall back to Iceberg cold path (post-TTL recovery)
    if (this.icebergClient) {
      this.logger.debug({ userId, sessionId }, 'Redis miss, querying Iceberg cold path');
      const coldMessages = await this.icebergClient.queryMessages(userId, sessionId, {
        startTime: timeRange?.start,
        endTime: timeRange?.end,
        limit,
      });
      return coldMessages.map((m) => ({
        id: m.id,
        userId: m.user_id,
        sessionId: m.session_id,
        role: m.role as StoredMessage['role'],
        content: m.content,
        timestamp: m.timestamp,
      }));
    }

    return [];
  }

  /**
   * Flush the full session from Redis to Iceberg as a single Parquet file.
   * Called once at session end — prevents small-file fragmentation.
   */
  async flushToIceberg(userId: string, sessionId: string, limit: number, channelType?: string): Promise<void> {
    if (!this.icebergClient) {
      return;
    }

    const messages = await this.getRecentMessages(userId, sessionId, limit, channelType);
    if (messages.length === 0) {
      return;
    }

    const icebergMessages = messages.map((m) => ({
      id: m.id,
      user_id: m.userId,
      session_id: m.sessionId,
      role: m.role,
      content: m.content,
      metadata: JSON.stringify(m.metadata || {}),
      timestamp: m.timestamp,
    }));

    await this.icebergClient.appendMessages(userId, sessionId, icebergMessages);
    this.logger.info({ userId, sessionId, count: icebergMessages.length }, 'Conversation flushed to Iceberg');
  }

  /**
   * Convert stored messages to LangChain message format
   */
  toLangChainMessages(messages: StoredMessage[]): BaseMessage[] {
    return messages.filter(msg => msg.role !== 'workspace').map((msg) => {
      switch (msg.role) {
        case 'user':
          return new HumanMessage(msg.content);
        case 'assistant':
          return new AIMessage(msg.content);
        case 'system':
          return new SystemMessage(msg.content);
        case 'workspace':
          return new HumanMessage(msg.content);
        default:
          throw new Error(`Unknown role: ${msg.role}`);
      }
    });
  }

  /**
   * Delete all messages for a session (Redis only, Iceberg handled separately)
   */
  async deleteSession(userId: string, sessionId: string, channelType?: string): Promise<void> {
    this.logger.info({ userId, sessionId }, 'Deleting session from Redis');
    const key = this.getRedisKey(userId, sessionId, channelType);
    await this.redis.del(key);
  }

  /**
   * Delete all messages for a user (GDPR compliance)
   */
  async deleteUserData(userId: string): Promise<void> {
    this.logger.info({ userId }, 'Deleting all user messages for GDPR compliance');

    const pattern = `conv:${userId}:*`;
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) {
      await this.redis.del(...keys);
    }

    this.logger.info({ userId }, 'User messages deleted from Redis - Iceberg GDPR delete not yet implemented');
  }

  /**
   * Get Redis key for conversation, namespaced by channel type
   */
  private getRedisKey(userId: string, sessionId: string, channelType?: string): string {
    return channelType
      ? `conv:${channelType}:${userId}:${sessionId}`
      : `conv:${userId}:${sessionId}`;
  }

  /**
   * Get conversation statistics
   */
  async getStats(userId: string, sessionId: string): Promise<{
    messageCount: number;
    firstMessage?: Date;
    lastMessage?: Date;
  }> {
    const key = this.getRedisKey(userId, sessionId);
    const count = await this.redis.llen(key);

    if (count === 0) {
      return { messageCount: 0 };
    }

    const messages = await this.getRecentMessages(userId, sessionId, count);
    const timestamps = messages.map((m) => m.timestamp / 1000);

    return {
      messageCount: count,
      firstMessage: new Date(Math.min(...timestamps)),
      lastMessage: new Date(Math.max(...timestamps)),
    };
  }
}
