import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { TransactionType } from '@prisma/client';
import { v4 as uuidv4 } from 'uuid';
import { format } from 'date-fns';
import { PrismaService } from '../../database/prisma.service';

export interface CreditOptions {
  childId: string;
  minutes: number;
  type: TransactionType;
  description: string;
  referenceId?: string;
  deviceId?: string;
  idempotencyKey?: string;
  metadata?: Record<string, any>;
}

export interface DebitOptions {
  childId: string;
  minutes: number;
  description: string;
  deviceId?: string;
  idempotencyKey?: string;
  metadata?: Record<string, any>;
}

export interface WalletSnapshot {
  balanceMinutes: number;
  dailyFreeMinutes: number;
  dailyConditionalMinutes: number;
  dailyMaxMinutes: number;
  usedMinutesToday: number;
  earnedConditionalToday: number;
  availableMinutes: number;
  stillUnlockableMinutes: number;
  lastResetDate: string;
}

@Injectable()
export class RewardEngine {
  private readonly logger = new Logger(RewardEngine.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Credita minutos na carteira do filho.
   * Toda operação gera uma transação imutável.
   * Idempotente: mesma chave = sem efeito duplicado.
   */
  async credit(options: CreditOptions): Promise<void> {
    const { childId, minutes, type, description, referenceId, deviceId, metadata } = options;
    const idempotencyKey = options.idempotencyKey || uuidv4();

    if (minutes <= 0) {
      throw new BadRequestException('Minutos de crédito devem ser positivos');
    }

    // Idempotência: verificar se transação já existe
    const existing = await this.prisma.timeTransaction.findUnique({
      where: { idempotencyKey },
    });
    if (existing) {
      this.logger.warn(`Transação duplicada ignorada: ${idempotencyKey}`);
      return;
    }

    await this.prisma.$transaction(async (tx) => {
      const wallet = await tx.timeWallet.findUnique({
        where: { childId },
      });

      if (!wallet) throw new BadRequestException('Carteira não encontrada');

      // Reset diário se necessário
      const today = format(new Date(), 'yyyy-MM-dd');
      let { balanceMinutes, usedMinutesToday, earnedConditionalToday } = wallet;

      if (wallet.lastResetDate !== today) {
        balanceMinutes = wallet.dailyFreeMinutes;
        usedMinutesToday = 0;
        earnedConditionalToday = 0;
        await tx.timeWallet.update({
          where: { childId },
          data: {
            balanceMinutes,
            usedMinutesToday: 0,
            earnedConditionalToday: 0,
            lastResetDate: today,
          },
        });
      }

      // Calcular limite máximo disponível para crédito condicional
      let actualMinutes = minutes;
      if (type === TransactionType.MISSION_REWARD || type === TransactionType.PARENT_BONUS) {
        const maxEarnable = wallet.dailyConditionalMinutes - earnedConditionalToday;
        const maxTotal = wallet.dailyMaxMinutes - usedMinutesToday - balanceMinutes;
        const cap = Math.min(maxEarnable, maxTotal, minutes);

        if (cap <= 0) {
          this.logger.log(`[RewardEngine] Limite máximo atingido para ${childId}. Crédito ignorado.`);
          return;
        }
        actualMinutes = cap;
      }

      const newBalance = balanceMinutes + actualMinutes;
      const newEarned = earnedConditionalToday + (
        type === TransactionType.MISSION_REWARD || type === TransactionType.PARENT_BONUS
          ? actualMinutes
          : 0
      );

      await tx.timeWallet.update({
        where: { childId },
        data: {
          balanceMinutes: newBalance,
          earnedConditionalToday: newEarned,
          lastResetDate: today,
        },
      });

      await tx.timeTransaction.create({
        data: {
          walletId: wallet.id,
          type,
          minutes: actualMinutes,
          balanceBefore: balanceMinutes,
          balanceAfter: newBalance,
          description,
          referenceId,
          idempotencyKey,
          deviceId,
          metadata,
        },
      });

      this.logger.log(
        `[RewardEngine] Crédito: ${actualMinutes}min → ${childId} | ${type} | ${description}`,
      );
    });
  }

  /**
   * Debita minutos da carteira (uso de app).
   */
  async debit(options: DebitOptions): Promise<void> {
    const { childId, minutes, description, deviceId, metadata } = options;
    const idempotencyKey = options.idempotencyKey || uuidv4();

    if (minutes <= 0) {
      throw new BadRequestException('Minutos de débito devem ser positivos');
    }

    const existing = await this.prisma.timeTransaction.findUnique({
      where: { idempotencyKey },
    });
    if (existing) return;

    await this.prisma.$transaction(async (tx) => {
      const wallet = await tx.timeWallet.findUnique({ where: { childId } });
      if (!wallet) throw new BadRequestException('Carteira não encontrada');

      const today = format(new Date(), 'yyyy-MM-dd');
      let { balanceMinutes, usedMinutesToday } = wallet;

      if (wallet.lastResetDate !== today) {
        balanceMinutes = wallet.dailyFreeMinutes;
        usedMinutesToday = 0;
        await tx.timeWallet.update({
          where: { childId },
          data: { balanceMinutes, usedMinutesToday: 0, lastResetDate: today },
        });
      }

      const actualDebit = Math.min(minutes, balanceMinutes);
      const newBalance = balanceMinutes - actualDebit;
      const newUsed = usedMinutesToday + actualDebit;

      await tx.timeWallet.update({
        where: { childId },
        data: { balanceMinutes: newBalance, usedMinutesToday: newUsed },
      });

      await tx.timeTransaction.create({
        data: {
          walletId: wallet.id,
          type: TransactionType.APP_USAGE,
          minutes: -actualDebit,
          balanceBefore: balanceMinutes,
          balanceAfter: newBalance,
          description,
          idempotencyKey,
          deviceId,
          metadata,
        },
      });
    });
  }

  /**
   * Snapshot atual da carteira com todos os campos calculados.
   */
  async getSnapshot(childId: string): Promise<WalletSnapshot> {
    const wallet = await this.prisma.timeWallet.findUnique({ where: { childId } });
    if (!wallet) throw new BadRequestException('Carteira não encontrada');

    const today = format(new Date(), 'yyyy-MM-dd');
    let { balanceMinutes, usedMinutesToday, earnedConditionalToday } = wallet;

    if (wallet.lastResetDate !== today) {
      balanceMinutes = wallet.dailyFreeMinutes;
      usedMinutesToday = 0;
      earnedConditionalToday = 0;
    }

    const stillUnlockableMinutes = Math.max(
      0,
      wallet.dailyConditionalMinutes - earnedConditionalToday,
    );

    return {
      balanceMinutes,
      dailyFreeMinutes: wallet.dailyFreeMinutes,
      dailyConditionalMinutes: wallet.dailyConditionalMinutes,
      dailyMaxMinutes: wallet.dailyMaxMinutes,
      usedMinutesToday,
      earnedConditionalToday,
      availableMinutes: balanceMinutes,
      stillUnlockableMinutes,
      lastResetDate: today,
    };
  }

  /**
   * Inicializar carteira para nova criança.
   */
  async initializeWallet(
    childId: string,
    options: { dailyFreeMinutes?: number; dailyConditionalMinutes?: number; dailyMaxMinutes?: number } = {},
  ): Promise<void> {
    const today = format(new Date(), 'yyyy-MM-dd');
    const dailyFreeMinutes = options.dailyFreeMinutes ?? 60;
    const dailyConditionalMinutes = options.dailyConditionalMinutes ?? 60;
    const dailyMaxMinutes = options.dailyMaxMinutes ?? 120;

    await this.prisma.timeWallet.upsert({
      where: { childId },
      create: {
        childId,
        balanceMinutes: dailyFreeMinutes,
        dailyFreeMinutes,
        dailyConditionalMinutes,
        dailyMaxMinutes,
        usedMinutesToday: 0,
        earnedConditionalToday: 0,
        lastResetDate: today,
      },
      update: {
        dailyFreeMinutes,
        dailyConditionalMinutes,
        dailyMaxMinutes,
      },
    });
  }

  /**
   * Reset diário da carteira (chamado por cron).
   */
  async processDailyReset(childId: string): Promise<void> {
    const today = format(new Date(), 'yyyy-MM-dd');
    const wallet = await this.prisma.timeWallet.findUnique({ where: { childId } });
    if (!wallet || wallet.lastResetDate === today) return;

    await this.prisma.$transaction(async (tx) => {
      // Expirar saldo não utilizado
      if (wallet.balanceMinutes > 0) {
        await tx.timeTransaction.create({
          data: {
            walletId: wallet.id,
            type: TransactionType.EXPIRATION,
            minutes: -wallet.balanceMinutes,
            balanceBefore: wallet.balanceMinutes,
            balanceAfter: 0,
            description: 'Expiração de saldo ao fim do dia',
            idempotencyKey: `expiry-${childId}-${wallet.lastResetDate}`,
          },
        });
      }

      // Reset com tempo livre do novo dia
      await tx.timeWallet.update({
        where: { childId },
        data: {
          balanceMinutes: wallet.dailyFreeMinutes,
          usedMinutesToday: 0,
          earnedConditionalToday: 0,
          lastResetDate: today,
        },
      });

      // Transação do tempo livre do novo dia
      await tx.timeTransaction.create({
        data: {
          walletId: wallet.id,
          type: TransactionType.FREE_DAILY,
          minutes: wallet.dailyFreeMinutes,
          balanceBefore: 0,
          balanceAfter: wallet.dailyFreeMinutes,
          description: 'Tempo livre diário',
          idempotencyKey: `free-daily-${childId}-${today}`,
        },
      });
    });

    this.logger.log(`[RewardEngine] Reset diário processado para ${childId}`);
  }
}
