import { Repository, FindOptionsWhere, LessThan, MoreThanOrEqual } from 'typeorm';
import { AIModel, AIConversation, AIMessage, AIPrompt, AIUsageLog, AITenantQuota } from '../entities';

export interface ConversationFilters {
  userId?: string;
  modelId?: string;
  status?: string;
  startDate?: Date;
  endDate?: Date;
}

export class AIService {
  constructor(
    private readonly modelRepository: Repository<AIModel>,
    private readonly conversationRepository: Repository<AIConversation>,
    private readonly messageRepository: Repository<AIMessage>,
    private readonly promptRepository: Repository<AIPrompt>,
    private readonly usageLogRepository: Repository<AIUsageLog>,
    private readonly quotaRepository: Repository<AITenantQuota>
  ) {}

  // ============================================
  // MODELS
  // ============================================

  async findAllModels(): Promise<AIModel[]> {
    return this.modelRepository.find({
      where: { isActive: true },
      order: { provider: 'ASC', name: 'ASC' },
    });
  }

  async findModel(id: string): Promise<AIModel | null> {
    return this.modelRepository.findOne({ where: { id } });
  }

  async findModelByCode(code: string): Promise<AIModel | null> {
    return this.modelRepository.findOne({ where: { code } });
  }

  async findModelsByProvider(provider: string): Promise<AIModel[]> {
    return this.modelRepository.find({
      where: { provider: provider as any, isActive: true },
      order: { name: 'ASC' },
    });
  }

  async findModelsByType(modelType: string): Promise<AIModel[]> {
    return this.modelRepository.find({
      where: { modelType: modelType as any, isActive: true },
      order: { name: 'ASC' },
    });
  }

  // ============================================
  // PROMPTS
  // ============================================

  async findAllPrompts(tenantId?: string): Promise<AIPrompt[]> {
    if (tenantId) {
      return this.promptRepository.find({
        where: [{ tenantId, isActive: true }, { isSystem: true, isActive: true }],
        order: { category: 'ASC', name: 'ASC' },
      });
    }
    return this.promptRepository.find({
      where: { isActive: true },
      order: { category: 'ASC', name: 'ASC' },
    });
  }

  async findPrompt(id: string): Promise<AIPrompt | null> {
    return this.promptRepository.findOne({ where: { id } });
  }

  async findPromptByCode(code: string, tenantId?: string): Promise<AIPrompt | null> {
    if (tenantId) {
      // Try tenant-specific first, then system prompt
      const tenantPrompt = await this.promptRepository.findOne({
        where: { code, tenantId, isActive: true },
      });
      if (tenantPrompt) return tenantPrompt;

      return this.promptRepository.findOne({
        where: { code, isSystem: true, isActive: true },
      });
    }
    return this.promptRepository.findOne({ where: { code, isActive: true } });
  }

  async createPrompt(
    tenantId: string,
    data: Partial<AIPrompt>,
    createdBy?: string
  ): Promise<AIPrompt> {
    const prompt = this.promptRepository.create({
      ...data,
      tenantId,
      createdBy,
      version: 1,
    });
    return this.promptRepository.save(prompt);
  }

  async updatePrompt(
    id: string,
    data: Partial<AIPrompt>,
    updatedBy?: string
  ): Promise<AIPrompt | null> {
    const prompt = await this.findPrompt(id);
    if (!prompt) return null;

    if (prompt.isSystem) {
      throw new Error('Cannot update system prompts');
    }

    Object.assign(prompt, data, { updatedBy, version: prompt.version + 1 });
    return this.promptRepository.save(prompt);
  }

  async incrementPromptUsage(id: string): Promise<void> {
    await this.promptRepository
      .createQueryBuilder()
      .update()
      .set({
        usageCount: () => 'usage_count + 1',
      })
      .where('id = :id', { id })
      .execute();
  }

  // ============================================
  // CONVERSATIONS
  // ============================================

  async findConversations(
    tenantId: string,
    filters: ConversationFilters = {},
    limit: number = 50
  ): Promise<AIConversation[]> {
    const where: FindOptionsWhere<AIConversation> = { tenantId };

    if (filters.userId) where.userId = filters.userId;
    if (filters.modelId) where.modelId = filters.modelId;
    if (filters.status) where.status = filters.status as any;

    return this.conversationRepository.find({
      where,
      order: { updatedAt: 'DESC' },
      take: limit,
    });
  }

  async findConversation(id: string): Promise<AIConversation | null> {
    return this.conversationRepository.findOne({
      where: { id },
      relations: ['messages'],
    });
  }

  async findUserConversations(
    tenantId: string,
    userId: string,
    limit: number = 20
  ): Promise<AIConversation[]> {
    return this.conversationRepository.find({
      where: { tenantId, userId },
      order: { updatedAt: 'DESC' },
      take: limit,
    });
  }

  async createConversation(
    tenantId: string,
    userId: string,
    data: Partial<AIConversation>
  ): Promise<AIConversation> {
    const conversation = this.conversationRepository.create({
      ...data,
      tenantId,
      userId,
      status: 'active',
    });
    return this.conversationRepository.save(conversation);
  }

  async updateConversation(
    id: string,
    data: Partial<AIConversation>
  ): Promise<AIConversation | null> {
    const conversation = await this.conversationRepository.findOne({ where: { id } });
    if (!conversation) return null;

    Object.assign(conversation, data);
    return this.conversationRepository.save(conversation);
  }

  async archiveConversation(id: string): Promise<boolean> {
    const result = await this.conversationRepository.update(id, { status: 'archived' });
    return (result.affected ?? 0) > 0;
  }

  // ============================================
  // MESSAGES
  // ============================================

  async findMessages(conversationId: string): Promise<AIMessage[]> {
    return this.messageRepository.find({
      where: { conversationId },
      order: { createdAt: 'ASC' },
    });
  }

  async addMessage(conversationId: string, data: Partial<AIMessage>): Promise<AIMessage> {
    const message = this.messageRepository.create({
      ...data,
      conversationId,
    });

    const savedMessage = await this.messageRepository.save(message);

    // Update conversation
    await this.conversationRepository
      .createQueryBuilder()
      .update()
      .set({
        messageCount: () => 'message_count + 1',
        totalTokens: () => `total_tokens + ${data.totalTokens || 0}`,
        updatedAt: new Date(),
      })
      .where('id = :id', { id: conversationId })
      .execute();

    return savedMessage;
  }

  async getConversationTokenCount(conversationId: string): Promise<number> {
    const result = await this.messageRepository
      .createQueryBuilder('message')
      .select('SUM(message.total_tokens)', 'total')
      .where('message.conversation_id = :conversationId', { conversationId })
      .getRawOne();

    return parseInt(result?.total) || 0;
  }

  // ============================================
  // USAGE & QUOTAS
  // ============================================

  async logUsage(tenantId: string, data: Partial<AIUsageLog>): Promise<AIUsageLog> {
    const log = this.usageLogRepository.create({
      ...data,
      tenantId,
    });
    return this.usageLogRepository.save(log);
  }

  async getUsageStats(
    tenantId: string,
    startDate: Date,
    endDate: Date
  ): Promise<{
    totalRequests: number;
    totalInputTokens: number;
    totalOutputTokens: number;
    totalCost: number;
    byModel: Record<string, { requests: number; tokens: number; cost: number }>;
  }> {
    const stats = await this.usageLogRepository
      .createQueryBuilder('log')
      .select('COUNT(*)', 'totalRequests')
      .addSelect('SUM(log.input_tokens)', 'totalInputTokens')
      .addSelect('SUM(log.output_tokens)', 'totalOutputTokens')
      .addSelect('SUM(log.cost_usd)', 'totalCost')
      .where('log.tenant_id = :tenantId', { tenantId })
      .andWhere('log.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .getRawOne();

    const byModelStats = await this.usageLogRepository
      .createQueryBuilder('log')
      .select('log.model_id', 'modelId')
      .addSelect('COUNT(*)', 'requests')
      .addSelect('SUM(log.input_tokens + log.output_tokens)', 'tokens')
      .addSelect('SUM(log.cost_usd)', 'cost')
      .where('log.tenant_id = :tenantId', { tenantId })
      .andWhere('log.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .groupBy('log.model_id')
      .getRawMany();

    const byModel: Record<string, { requests: number; tokens: number; cost: number }> = {};
    for (const stat of byModelStats) {
      byModel[stat.modelId] = {
        requests: parseInt(stat.requests) || 0,
        tokens: parseInt(stat.tokens) || 0,
        cost: parseFloat(stat.cost) || 0,
      };
    }

    return {
      totalRequests: parseInt(stats?.totalRequests) || 0,
      totalInputTokens: parseInt(stats?.totalInputTokens) || 0,
      totalOutputTokens: parseInt(stats?.totalOutputTokens) || 0,
      totalCost: parseFloat(stats?.totalCost) || 0,
      byModel,
    };
  }

  async getTenantQuota(tenantId: string): Promise<AITenantQuota | null> {
    return this.quotaRepository.findOne({ where: { tenantId } });
  }

  async updateTenantQuota(
    tenantId: string,
    data: Partial<AITenantQuota>
  ): Promise<AITenantQuota> {
    let quota = await this.getTenantQuota(tenantId);

    if (!quota) {
      quota = this.quotaRepository.create({
        tenantId,
        ...data,
      });
    } else {
      Object.assign(quota, data);
    }

    return this.quotaRepository.save(quota);
  }

  async incrementQuotaUsage(
    tenantId: string,
    requestCount: number,
    tokenCount: number,
    costUsd: number
  ): Promise<void> {
    await this.quotaRepository
      .createQueryBuilder()
      .update()
      .set({
        currentRequests: () => `current_requests + ${requestCount}`,
        currentTokens: () => `current_tokens + ${tokenCount}`,
        currentCost: () => `current_cost + ${costUsd}`,
      })
      .where('tenant_id = :tenantId', { tenantId })
      .execute();
  }

  async checkQuotaAvailable(tenantId: string): Promise<{
    available: boolean;
    reason?: string;
  }> {
    const quota = await this.getTenantQuota(tenantId);
    if (!quota) return { available: true };

    if (quota.monthlyRequestLimit && quota.currentRequests >= quota.monthlyRequestLimit) {
      return { available: false, reason: 'Monthly request limit reached' };
    }

    if (quota.monthlyTokenLimit && quota.currentTokens >= quota.monthlyTokenLimit) {
      return { available: false, reason: 'Monthly token limit reached' };
    }

    if (quota.monthlyCostLimit && quota.currentCost >= quota.monthlyCostLimit) {
      return { available: false, reason: 'Monthly spend limit reached' };
    }

    return { available: true };
  }

  async resetMonthlyQuotas(): Promise<number> {
    const result = await this.quotaRepository.update(
      {},
      {
        currentRequests: 0,
        currentTokens: 0,
        currentCost: 0,
      }
    );
    return result.affected ?? 0;
  }
}
