<?php

namespace App\Services;

use App\Models\RecurringTemplate;
use App\Models\RecurringInstance;
use App\Models\Transaction;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class RecurringService
{
    /**
     * Horizonte padrão de geração (em meses)
     */
    private const DEFAULT_HORIZON_MONTHS = 12;

    /**
     * Cria um template de recorrência a partir de uma transação existente
     */
    public function createFromTransaction(
        Transaction $transaction,
        string $frequency,
        array $options = []
    ): RecurringTemplate {
        $template = RecurringTemplate::create([
            'user_id' => $transaction->user_id,
            'source_transaction_id' => $transaction->id,
            'name' => $options['name'] ?? $transaction->description,
            'description' => $options['description'] ?? null,
            'frequency' => $frequency,
            'frequency_interval' => $options['frequency_interval'] ?? 1,
            'day_of_month' => $options['day_of_month'] ?? $transaction->planned_date->day,
            'day_of_week' => $options['day_of_week'] ?? $transaction->planned_date->dayOfWeek,
            'start_date' => $options['start_date'] ?? $transaction->planned_date,
            'end_date' => $options['end_date'] ?? null,
            'max_occurrences' => $options['max_occurrences'] ?? null,
            'account_id' => $transaction->account_id,
            'category_id' => $transaction->category_id,
            'cost_center_id' => $transaction->cost_center_id,
            'type' => $transaction->type,
            'planned_amount' => $transaction->planned_amount,
            'transaction_description' => $transaction->description,
            'notes' => $transaction->notes,
            'is_active' => true,
        ]);

        // Gerar instâncias iniciais
        $this->generateInstances($template);

        return $template->fresh(['instances', 'account', 'category']);
    }

    /**
     * Cria um template de recorrência manualmente
     */
    public function createTemplate(int $userId, array $data): RecurringTemplate
    {
        $template = RecurringTemplate::create([
            'user_id' => $userId,
            'source_transaction_id' => $data['source_transaction_id'] ?? null,
            'name' => $data['name'],
            'description' => $data['description'] ?? null,
            'frequency' => $data['frequency'],
            'frequency_interval' => $data['frequency_interval'] ?? 1,
            'day_of_month' => $data['day_of_month'] ?? null,
            'day_of_week' => $data['day_of_week'] ?? null,
            'start_date' => $data['start_date'],
            'end_date' => $data['end_date'] ?? null,
            'max_occurrences' => $data['max_occurrences'] ?? null,
            'account_id' => $data['account_id'],
            'category_id' => $data['category_id'] ?? null,
            'cost_center_id' => $data['cost_center_id'] ?? null,
            'type' => $data['type'],
            'planned_amount' => $data['planned_amount'],
            'transaction_description' => $data['transaction_description'],
            'notes' => $data['notes'] ?? null,
            'is_active' => true,
        ]);

        // Gerar instâncias iniciais
        $this->generateInstances($template);

        return $template->fresh(['instances', 'account', 'category']);
    }

    /**
     * Gera instâncias para um template até o horizonte definido
     */
    public function generateInstances(RecurringTemplate $template, ?int $horizonMonths = null): int
    {
        if (!$template->canGenerateMore()) {
            return 0;
        }

        $horizonMonths = $horizonMonths ?? self::DEFAULT_HORIZON_MONTHS;
        $horizonDate = now()->addMonths($horizonMonths)->endOfMonth();
        
        // Determinar data inicial para geração
        $startDate = $template->last_generated_date 
            ? $this->calculateNextDate($template, $template->last_generated_date)
            : Carbon::parse($template->start_date);

        $generated = 0;
        $currentDate = $startDate;
        $occurrenceNumber = $template->occurrences_generated;

        while ($currentDate->lte($horizonDate)) {
            // Verificar limites
            if ($template->max_occurrences !== null && $occurrenceNumber >= $template->max_occurrences) {
                break;
            }

            if ($template->end_date !== null && $currentDate->gt($template->end_date)) {
                break;
            }

            // Verificar se já existe instância para esta data
            $exists = RecurringInstance::where('recurring_template_id', $template->id)
                ->where('due_date', $currentDate->toDateString())
                ->exists();

            if (!$exists) {
                $occurrenceNumber++;
                
                RecurringInstance::create([
                    'user_id' => $template->user_id,
                    'recurring_template_id' => $template->id,
                    'occurrence_number' => $occurrenceNumber,
                    'due_date' => $currentDate,
                    'planned_amount' => $template->planned_amount,
                    'status' => RecurringInstance::STATUS_PENDING,
                ]);

                $generated++;
            }

            // Próxima data
            $currentDate = $this->calculateNextDate($template, $currentDate);
        }

        // Atualizar template
        if ($generated > 0) {
            $template->update([
                'last_generated_date' => RecurringInstance::where('recurring_template_id', $template->id)
                    ->max('due_date'),
                'occurrences_generated' => $occurrenceNumber,
            ]);
        }

        return $generated;
    }

    /**
     * Calcula a próxima data de vencimento baseada na frequência
     * IMPORTANTE: Ajusta dias para meses curtos (ex: 31 → 28 em fevereiro)
     */
    public function calculateNextDate(RecurringTemplate $template, Carbon $fromDate): Carbon
    {
        $interval = $template->frequency_interval ?? 1;
        $nextDate = $fromDate->copy();

        switch ($template->frequency) {
            case 'daily':
                $nextDate->addDays($interval);
                break;

            case 'weekly':
                $nextDate->addWeeks($interval);
                // Se tem dia da semana definido, ajustar
                if ($template->day_of_week !== null) {
                    $nextDate->next($template->day_of_week);
                }
                break;

            case 'biweekly':
                $nextDate->addWeeks(2 * $interval);
                break;

            case 'monthly':
            case 'bimonthly':
            case 'quarterly':
            case 'semiannual':
            case 'annual':
                $months = match($template->frequency) {
                    'monthly' => 1,
                    'bimonthly' => 2,
                    'quarterly' => 3,
                    'semiannual' => 6,
                    'annual' => 12,
                } * $interval;

                $nextDate = $this->addMonthsWithDayAdjustment(
                    $fromDate,
                    $months,
                    $template->day_of_month
                );
                break;
        }

        return $nextDate;
    }

    /**
     * Adiciona meses à data mantendo o dia do mês correto
     * Se o dia não existe no mês destino, usa o último dia disponível
     * 
     * Exemplo: 31/Jan + 1 mês = 28/Fev (ou 29 em bissexto)
     *          30/Jan + 1 mês = 28/Fev
     *          29/Jan + 1 mês = 28/Fev (ou 29 em bissexto)
     */
    private function addMonthsWithDayAdjustment(Carbon $date, int $months, ?int $preferredDay = null): Carbon
    {
        $targetDay = $preferredDay ?? $date->day;
        
        // Avançar os meses
        $newDate = $date->copy()->addMonths($months);
        
        // Obter o último dia do mês destino
        $lastDayOfMonth = $newDate->copy()->endOfMonth()->day;
        
        // Se o dia preferido é maior que o último dia do mês, usar o último dia
        $actualDay = min($targetDay, $lastDayOfMonth);
        
        // Definir o dia correto
        $newDate->day($actualDay);
        
        return $newDate;
    }

    /**
     * Concilia uma instância com uma transação existente
     */
    public function reconcileWithTransaction(
        RecurringInstance $instance,
        Transaction $transaction,
        ?string $notes = null
    ): RecurringInstance {
        return DB::transaction(function () use ($instance, $transaction, $notes) {
            $instance->update([
                'status' => RecurringInstance::STATUS_PAID,
                'transaction_id' => $transaction->id,
                'paid_at' => $transaction->effective_date ?? $transaction->planned_date,
                'paid_amount' => $transaction->amount ?? $transaction->planned_amount,
                'paid_notes' => $notes,
            ]);

            // Atualizar transação com link reverso
            $transaction->update([
                'recurring_instance_id' => $instance->id,
            ]);

            // Gerar próximas instâncias se necessário
            $this->generateInstances($instance->template);

            return $instance->fresh(['template', 'transaction']);
        });
    }

    /**
     * Marca como pago criando uma nova transação
     */
    public function markAsPaid(
        RecurringInstance $instance,
        array $transactionData = []
    ): RecurringInstance {
        return DB::transaction(function () use ($instance, $transactionData) {
            $template = $instance->template;

            // Criar transação
            $transaction = Transaction::create([
                'user_id' => $template->user_id,
                'account_id' => $template->account_id,
                'category_id' => $template->category_id,
                'cost_center_id' => $template->cost_center_id,
                'type' => $template->type,
                'planned_amount' => $transactionData['amount'] ?? $instance->planned_amount,
                'amount' => $transactionData['amount'] ?? $instance->planned_amount,
                'planned_date' => $instance->due_date,
                'effective_date' => $transactionData['effective_date'] ?? now(),
                'description' => $transactionData['description'] ?? $template->transaction_description,
                'notes' => $transactionData['notes'] ?? $template->notes,
                'status' => 'completed',
                'recurring_instance_id' => $instance->id,
            ]);

            // Atualizar instância
            $instance->update([
                'status' => RecurringInstance::STATUS_PAID,
                'transaction_id' => $transaction->id,
                'paid_at' => $transaction->effective_date,
                'paid_amount' => $transaction->amount,
                'paid_notes' => $transactionData['notes'] ?? null,
            ]);

            // Gerar próximas instâncias se necessário
            $this->generateInstances($template);

            return $instance->fresh(['template', 'transaction']);
        });
    }

    /**
     * Pula uma instância
     */
    public function skipInstance(RecurringInstance $instance, ?string $reason = null): RecurringInstance
    {
        $instance->update([
            'status' => RecurringInstance::STATUS_SKIPPED,
            'paid_notes' => $reason,
        ]);

        // Gerar próximas instâncias se necessário
        $this->generateInstances($instance->template);

        return $instance->fresh();
    }

    /**
     * Cancela uma instância
     */
    public function cancelInstance(RecurringInstance $instance, ?string $reason = null): RecurringInstance
    {
        $instance->update([
            'status' => RecurringInstance::STATUS_CANCELLED,
            'paid_notes' => $reason,
        ]);

        return $instance->fresh();
    }

    /**
     * Pausa um template (para de gerar novas instâncias)
     */
    public function pauseTemplate(RecurringTemplate $template): RecurringTemplate
    {
        $template->update(['is_active' => false]);
        return $template->fresh();
    }

    /**
     * Reativa um template e gera instâncias faltantes
     */
    public function resumeTemplate(RecurringTemplate $template): RecurringTemplate
    {
        $template->update(['is_active' => true]);
        $this->generateInstances($template);
        return $template->fresh(['instances']);
    }

    /**
     * Regenera instâncias pendentes para todos os templates ativos de um usuário
     * Útil para rodar em um job diário
     */
    public function regenerateAllForUser(int $userId): int
    {
        $templates = RecurringTemplate::where('user_id', $userId)
            ->where('is_active', true)
            ->get();

        $totalGenerated = 0;
        foreach ($templates as $template) {
            $totalGenerated += $this->generateInstances($template);
        }

        return $totalGenerated;
    }

    /**
     * Busca transações candidatas para conciliar com uma instância
     */
    public function findCandidateTransactions(RecurringInstance $instance, int $daysTolerance = 7): \Illuminate\Support\Collection
    {
        $template = $instance->template;
        
        return Transaction::where('user_id', $template->user_id)
            ->where('account_id', $template->account_id)
            ->where('type', $template->type)
            ->whereNull('recurring_instance_id') // Não está vinculada a outra recorrência
            ->where('status', 'completed')
            ->whereBetween('effective_date', [
                $instance->due_date->copy()->subDays($daysTolerance),
                $instance->due_date->copy()->addDays($daysTolerance),
            ])
            ->whereBetween('amount', [
                $instance->planned_amount * 0.9, // 10% de tolerância
                $instance->planned_amount * 1.1,
            ])
            ->orderByRaw('ABS(DATEDIFF(effective_date, ?))', [$instance->due_date])
            ->limit(10)
            ->get();
    }
}
