<?php

namespace App\Services;

use App\Models\Business;
use App\Models\Customer;
use App\Models\LoanInstallment;
use App\Models\NotificationTemplate;
use App\Models\ReminderNotificationLog;
use App\Services\Notifications\ReminderMessageRenderer;
use App\Services\Notifications\ReminderProviderRegistry;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class LoanReminderService
{
    public function __construct(
        private ReminderMessageRenderer $renderer,
        private ReminderProviderRegistry $providers,
        private ModuleManager $modules,
    ) {}

    /** @return array{generated:int,delivered:int,failed:int,skipped:int} */
    public function dispatch(CarbonInterface|string $date): array
    {
        $target=$date instanceof CarbonInterface ? CarbonImmutable::instance($date)->startOfDay() : CarbonImmutable::parse($date)->startOfDay();
        $stats=['generated'=>0,'delivered'=>0,'failed'=>0,'skipped'=>0];

        $installments=LoanInstallment::query()
            ->with(['loan.customer','loan.branch.business'])
            ->where('is_active',true)
            ->where('remaining','>','0.00')
            ->whereHas('loan',fn($q)=>$q->where('status','active'))
            ->orderBy('due_date')->orderBy('id')->get();

        foreach($installments as $installment){
            $loan=$installment->loan; $business=$loan->branch->business;
            if(!$business->reminders_enabled){ $stats['skipped']++; continue; }
            $due=CarbonImmutable::parse($installment->due_date)->startOfDay();
            $daysFromDue=(int)$due->diff($target)->format('%r%a');
            $type=$this->reminderType($business,$daysFromDue);
            if($type===null){ $stats['skipped']++; continue; }

            foreach($this->channels($business) as $channel){
                $template=NotificationTemplate::query()->where('business_id',$business->id)->where('event_type',$type)->where('channel',$channel)->where('active',true)->first();
                if(!$template){ $stats['skipped']++; continue; }
                $rendered=$this->renderer->render($template,$business,$loan,$installment,$daysFromDue);
                $key=hash('sha256',implode('|',[$installment->id,$type,$target->toDateString(),$channel]));
                $now=now();
                DB::table('notification_logs')->insertOrIgnore([
                    'business_id'=>$business->id,'branch_id'=>$loan->branch_id,'customer_id'=>$loan->customer_id,'loan_id'=>$loan->id,
                    'loan_installment_id'=>$installment->id,'notification_template_id'=>$template->id,'reminder_type'=>$type,'days_from_due'=>$daysFromDue,
                    'channel'=>$channel,'recipient'=>$this->recipient($channel,$loan->customer),'subject'=>$rendered['subject'],'message'=>$rendered['message'],
                    'scheduled_for'=>$target->toDateString(),'status'=>'pending','attempts'=>0,'idempotency_key'=>$key,'created_at'=>$now,'updated_at'=>$now,
                ]);
                $log=ReminderNotificationLog::where('idempotency_key',$key)->firstOrFail();
                if(!$this->claimInitialAttempt($log)){ $stats['skipped']++; continue; }
                $stats['generated']++;
                $this->deliver($log->fresh('customer')) ? $stats['delivered']++ : $stats['failed']++;
            }
        }

        return $stats;
    }

    public function retry(ReminderNotificationLog $log): ReminderNotificationLog
    {
        if(!$this->channelEnabled($log->channel,(int)$log->business_id)) {
            $label=$log->channel==='sms' ? 'SMS Messaging' : 'WhatsApp Messaging';
            throw ValidationException::withMessages(['status'=>$label.' is disabled in System Settings.']);
        }

        $logId=$log->id;
        $claimed=DB::transaction(function() use($logId): bool {
            $locked=ReminderNotificationLog::query()->lockForUpdate()->findOrFail($logId);
            if($locked->status!=='failed') throw ValidationException::withMessages(['status'=>'Only failed notifications can be retried.']);
            $locked->update(['status'=>'pending','attempts'=>$locked->attempts+1,'last_attempt_at'=>now(),'error'=>null]);
            return true;
        });
        if($claimed) $this->deliver(ReminderNotificationLog::with('customer')->findOrFail($logId));
        return ReminderNotificationLog::findOrFail($logId);
    }

    private function claimInitialAttempt(ReminderNotificationLog $log): bool
    {
        return DB::transaction(function() use($log): bool {
            $locked=ReminderNotificationLog::query()->lockForUpdate()->findOrFail($log->id);
            if($locked->status!=='pending' || $locked->attempts!==0) return false;
            $locked->update(['attempts'=>1,'last_attempt_at'=>now()]);
            return true;
        });
    }

    private function deliver(ReminderNotificationLog $log): bool
    {
        $result=$this->providers->for($log->channel)->send($log,$log->customer);
        $log->update([
            'status'=>$result->success ? 'delivered' : 'failed',
            'provider_response'=>$result->response,
            'error'=>$result->success ? null : $result->error,
            'delivered_at'=>$result->success ? now() : null,
        ]);
        return $result->success;
    }

    private function reminderType(Business $business, int $daysFromDue): ?string
    {
        if($daysFromDue<0 && in_array(abs($daysFromDue),$this->preDueDays($business),true)) return 'pre_due';
        if($daysFromDue===0 && $business->reminder_due_date_enabled) return 'due';
        if($daysFromDue>0 && $business->reminder_overdue_enabled && $this->isOverdueOccurrence($daysFromDue,(string)$business->reminder_overdue_frequency)) return 'overdue';
        return null;
    }

    /** @return list<int> */
    private function preDueDays(Business $business): array
    {
        if($business->reminder_pre_due_days===null) return [3,1];
        return collect($business->reminder_pre_due_days)->map(fn($day)=>(int)$day)->filter(fn($day)=>$day>0)->unique()->values()->all();
    }

    /** @return list<string> */
    private function channels(Business $business): array
    {
        if($business->reminder_channels===null) return ['in_system'];
        return collect($business->reminder_channels)
            ->filter(fn($channel)=>in_array($channel,NotificationTemplate::CHANNELS,true))
            ->filter(fn($channel)=>$this->channelEnabled((string)$channel,(int)$business->id))
            ->unique()->values()->all();
    }

    private function channelEnabled(string $channel, int $businessId): bool
    {
        return match($channel){
            'sms'=>$this->modules->enabled('sms_messaging',$businessId),
            'whatsapp'=>$this->modules->enabled('whatsapp_messaging',$businessId),
            default=>true,
        };
    }

    private function isOverdueOccurrence(int $daysOverdue, string $frequency): bool
    {
        $interval=match($frequency){'every_2_days'=>2,'every_3_days'=>3,'weekly'=>7,default=>1};
        return $daysOverdue>=1 && (($daysOverdue-1)%$interval===0);
    }

    private function recipient(string $channel, Customer $customer): string
    {
        return match($channel){
            'email'=>(string)($customer->email ?: '[missing email]'),
            'in_system'=>$customer->customer_number,
            default=>$customer->phone,
        };
    }
}
