<?php

namespace App\Services;

use App\Models\{Assistance, Membership_users, Payment_plan, Sale, Sale_details};
use App\Services\{RateService, SalesService};
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
use App\Traits\GetCompanyInfo;

class ProcesoService
{
    use GetCompanyInfo;

    private const PLAN_MONTHLY_INSTALLMENTS = [
        'semanal' => 4,
        'quincenal' => 2,
        'mensual' => 1,
    ];

    public function updateMembershipSales(): array
    {
        try {
            DB::beginTransaction();

            $stats = [
                'active_memberships' => 0,
                'created_sales' => 0,
                'linked_existing_sales' => 0,
                'skipped_no_company' => 0,
                'skipped_no_people' => 0,
                'skipped_invalid_membership' => 0,
                'skipped_current_sale_not_expired' => 0,
                'skipped_already_processed_month' => 0,
            ];

            $defaultCompanyId = $this->getCompanyInfo()?->id;

            // 1) Obtener membresias activas que deben facturarse.y que price sea mayor a 0.
            $activeMemberships = Membership_users::query()
                ->where('is_active', 1)
                ->whereHas('membership', function ($query) {
                    $query->where('days', '>=', 2)
                          ->where('price', '>', 0);
                })
                ->with(['user', 'membership', 'payment_plan:id,plan_name'])
                ->get();

            $stats['active_memberships'] = $activeMemberships->count();
            if ($activeMemberships->isEmpty()) {
                Log::info('No hay membership_users activas con membresia >= 2 dias para procesar.');
            }


            $salesService = new SalesService();
            $saleScheduleService = new SaleScheduleService();
            $rateService = new RateService();

            // 2) Fijar tasa de conversion usada para todos los cargos del proceso.
            $rate = $rateService->getActiveRate();
            $usdRate = $rate ? (float) $rate->usd_rate : 1.0;
            if ($usdRate <= 0) {
                $usdRate = 1.0;
            }

            foreach ($activeMemberships as $membership) {
                $membershipModel = $membership->membership;
                if (! $membershipModel) {
                    $stats['skipped_invalid_membership']++;
                    Log::warning('Membresia activa omitida por membership_id invalido.', [
                        'membership_user_id' => (int) $membership->id,
                        'membership_id' => (int) $membership->membership_id,
                    ]);
                    continue;
                }

                // Solo generar nueva venta cuando la venta vigente de membresia este vencida.
                // La venta vigente se determina por sale_details de membresia (membership_user_id)
                // excluyendo detalles de productos (product_id no nulo).
                $currentMembershipSaleId = Sale_details::query()
                    ->where('membership_user_id', (int) $membership->id)
                    ->whereNull('product_id')
                    ->latest('id')
                    ->value('sale_id');

                if ($currentMembershipSaleId) {
                    if ((int) ($membership->sale_id ?? 0) !== (int) $currentMembershipSaleId) {
                        $membership->sale_id = (int) $currentMembershipSaleId;
                        $membership->save();
                    }

                    $currentSaleExpirationDate = Sale::query()
                        ->whereKey((int) $currentMembershipSaleId)
                        ->value('expiration_date');

                    if (! empty($currentSaleExpirationDate)) {
                        $isCurrentSaleExpired = Carbon::parse($currentSaleExpirationDate)
                            ->endOfDay()
                            ->lt(now());

                        if (! $isCurrentSaleExpired) {
                            $stats['skipped_current_sale_not_expired']++;
                            continue;
                        }
                    }
                }

                // 3) Resolver company y persona del usuario.
                $companyId = $this->getCompanyInfo((int) $membership->user_id)?->id
                    ?? $defaultCompanyId;
                if (! $companyId) {
                    $stats['skipped_no_company']++;
                    Log::warning('Membresia activa omitida por company_id no encontrado para el usuario.', [
                        'membership_user_id' => (int) $membership->id,
                        'user_id' => (int) $membership->user_id,
                    ]);
                    continue;
                }

                // 4) Evitar duplicados del mismo mes por membership_user_id.
                $monthStart = now()->startOfMonth();
                $monthEnd = now()->endOfMonth();
                $alreadyCreatedThisMonth = Sale_details::query()
                    ->where('membership_user_id', (int) $membership->id)
                    ->whereNull('product_id')
                    ->whereHas('sale', function ($query) use ($monthStart, $monthEnd) {
                        $query->whereBetween('sale.created_at', [$monthStart, $monthEnd]);
                    })
                    ->exists();

                if ($alreadyCreatedThisMonth) {
                    $stats['skipped_already_processed_month']++;

                    if (empty($membership->sale_id)) {
                        $existingSaleId = Sale_details::query()
                            ->where('membership_user_id', (int) $membership->id)
                            ->whereNull('product_id')
                            ->whereHas('sale', function ($query) use ($monthStart, $monthEnd) {
                                $query->whereBetween('sale.created_at', [$monthStart, $monthEnd]);
                            })
                            ->latest('id')
                            ->value('sale_id');

                        if ($existingSaleId) {
                            $membership->sale_id = (int) $existingSaleId;
                            $membership->save();
                            $stats['linked_existing_sales']++;
                        }
                    }

                    continue;
                }

                // 5) Calcular plan, cuotas mensuales y segmentos de dias.
                $baseTotal = round((float) ($membershipModel->price ?? 0), 2);
                $totalWithRate = round($baseTotal * $usdRate, 2);

                $peopleId = (int) ($membership->user?->people_id ?? 0);
                if ($peopleId <= 0) {
                    $stats['skipped_no_people']++;
                    Log::warning('Membresia activa omitida por people_id no encontrado.', [
                        'membership_user_id' => (int) $membership->id,
                        'user_id' => (int) $membership->user_id,
                    ]);
                    continue;
                }

                $membershipDays = max(1, (int) ($membershipModel->days ?? 0));
                $paymentPlanName = $this->normalizePlanName($membership->payment_plan?->plan_name);
                if (! $paymentPlanName && ! empty($membership->payment_plan_id)) {
                    $paymentPlanName = $this->normalizePlanName(Payment_plan::query()
                        ->whereKey((int) $membership->payment_plan_id)
                        ->value('plan_name'));
                }

                $installments = $this->resolveInstallmentsByPlanAndDays($paymentPlanName, $membershipDays);
                $segments = $this->buildMonthlySegmentsByInstallments($membershipDays, $installments);
                $baseAmountsBySegment = $this->splitAmountBySegments($baseTotal, $segments);
                $totalAmountsBySegment = $this->splitAmountBySegments($totalWithRate, $segments);

                // 6) Crear sale y sale_details por cada cuota del mes.
                $segmentBaseDate = now();
                $firstSaleId = null;

                foreach ($segments as $segmentIndex => $segmentDays) {
                    $saleDates = $saleScheduleService->calculateDatesForSegment(
                        $paymentPlanName,
                        (int) $segmentDays,
                        $segmentBaseDate
                    );

                    $segmentBaseTotal = (float) ($baseAmountsBySegment[$segmentIndex] ?? 0);
                    $segmentTotalWithRate = (float) ($totalAmountsBySegment[$segmentIndex] ?? 0);

                    $saleIdentifier = $salesService->getNewSaleIdentifier((int) $companyId);
                    $sale = Sale::create([
                        'sale_identifier' => $saleIdentifier,
                        'people_id' => $peopleId,
                        'user_id' => (int) $membership->user_id,
                        'company_id' => (int) $companyId,
                        'status' => 'pendiente',
                        'total' => $segmentTotalWithRate,
                        'debt' => $segmentTotalWithRate,
                        'aux_total' => $segmentBaseTotal,
                        'aux_debt' => $segmentBaseTotal,
                        'amount_rate' => $usdRate,
                        'expiration_date' => $saleDates['expiration_date'],
                        'alert_date' => $saleDates['alert_date'],
                    ]);

                    Sale_details::create([
                        'sale_id' => (int) $sale->id,
                        'product_id' => null,
                        'membership_user_id' => (int) $membership->id,
                        'quantity' => 1,
                        'unit_price' => $segmentTotalWithRate,
                        'subtotal' => $segmentTotalWithRate,
                        'aux_unit_price' => $segmentBaseTotal,
                        'aux_subtotal' => $segmentBaseTotal,
                    ]);

                    if ($firstSaleId === null) {
                        $firstSaleId = (int) $sale->id;
                    }

                    $segmentBaseDate = ($saleDates['expiration_date'] instanceof Carbon)
                        ? $saleDates['expiration_date']->copy()
                        : now();

                    $stats['created_sales']++;
                }

                // 7) Guardar referencia a la primera venta creada en el mes.
                if ($firstSaleId) {
                    $membership->sale_id = $firstSaleId;
                }
                $membership->save();
            }

            DB::commit();
            return $stats;
        } catch (Throwable $exception) {
            DB::rollBack();
            Log::error('Error al actualizar las ventas de membresías: ' . $exception->getMessage());
            throw $exception;
        }
    }

    private function resolveInstallmentsByPlanAndDays(?string $planName, int $membershipDays): int
    {
        $normalizedPlan = $this->normalizePlanName($planName);
        $monthlyInstallments = (int) (self::PLAN_MONTHLY_INSTALLMENTS[$normalizedPlan] ?? 1);

        $periodDaysByPlan = [
            'semanal' => 7,
            'quincenal' => 15,
            'mensual' => 30,
        ];

        $periodDays = (int) ($periodDaysByPlan[$normalizedPlan] ?? max(1, $membershipDays));
        $maxByMembershipDays = max(1, (int) ceil(max(1, $membershipDays) / max(1, $periodDays)));

        return max(1, min($monthlyInstallments, $maxByMembershipDays));
    }

    private function buildMonthlySegmentsByInstallments(int $membershipDays, int $installments): array
    {
        $days = max(1, $membershipDays);
        $count = max(1, $installments);

        if ($count === 1) {
            return [$days];
        }

        $baseDays = intdiv($days, $count);
        $remainder = $days % $count;
        $segments = [];

        for ($i = 0; $i < $count; $i++) {
            $segmentDays = $baseDays;
            if ($remainder > 0) {
                $segmentDays++;
                $remainder--;
            }

            $segments[] = max(1, $segmentDays);
        }

        return $segments;
    }

    private function splitAmountBySegments(float $totalAmount, array $segments): array
    {
        if (empty($segments)) {
            return [round($totalAmount, 2)];
        }

        $totalDays = array_sum(array_map(fn ($segment) => max(0, (int) $segment), $segments));
        if ($totalDays <= 0) {
            return [round($totalAmount, 2)];
        }

        $totalCents = (int) round($totalAmount * 100);
        $allocatedCents = 0;
        $splitAmounts = [];
        $lastIndex = count($segments) - 1;

        foreach ($segments as $index => $segmentDays) {
            $normalizedSegmentDays = max(0, (int) $segmentDays);

            if ($index === $lastIndex) {
                $segmentCents = $totalCents - $allocatedCents;
            } else {
                $segmentCents = (int) floor(($totalCents * $normalizedSegmentDays) / $totalDays);
                $allocatedCents += $segmentCents;
            }

            $splitAmounts[] = round($segmentCents / 100, 2);
        }

        return $splitAmounts;
    }

    private function normalizePlanName(?string $planName): string
    {
        $normalized = mb_strtolower(trim((string) $planName));

        $aliases = [
            'quinccenal' => 'quincenal',
            'quinsenal' => 'quincenal',
        ];

        return $aliases[$normalized] ?? $normalized;
    }
    public function updateMembershipSalesOnedays(): array
    {
        
        try {
            DB::beginTransaction();

            $stats = [
                'active_daily_memberships' => 0,
                'assistance_records_today' => 0,
                'existing_sales_today' => 0,
                'created_sales' => 0,
                'linked_existing_sales' => 0,
                'skipped_no_company' => 0,
                'skipped_no_people' => 0,
                'skipped_invalid_membership' => 0,
                'skipped_no_assistance_today' => 0,
                'skipped_already_synced' => 0,
            ];

            $currentMonth = now()->month;
            $currentYear = now()->year;
            $defaultCompanyId = $this->getCompanyInfo()?->id;

            $dailyMemberships = Membership_users::query()
                ->where('is_active', 1)
                ->whereHas('membership', function ($query) {
                    $query->where('days', 1);
                })
                ->with(['user', 'membership', 'payment_plan:id,plan_name'])
                ->get();

            $stats['active_daily_memberships'] = $dailyMemberships->count();

            if ($dailyMemberships->isEmpty()) {
                Log::info('No hay membership_users activas con membresia diaria (days = 1) para procesar.');
            }

            $salesService = new SalesService();
            $saleScheduleService = new SaleScheduleService();
            $rateService = new RateService();

            $rate = $rateService->getActiveRate();
            $usdRate = $rate ? (float) $rate->usd_rate : 1.0;
            if ($usdRate <= 0) {
                $usdRate = 1.0;
            }

            foreach ($dailyMemberships as $membershipUser) {
                $membershipModel = $membershipUser->membership;

                if (! $membershipModel || (int) ($membershipModel->days ?? 0) !== 1) {
                    $stats['skipped_invalid_membership']++;
                    Log::warning('Membresia diaria omitida por membership_id invalido o days distinto de 1.', [
                        'membership_user_id' => (int) $membershipUser->id,
                        'membership_id' => (int) $membershipUser->membership_id,
                    ]);
                    continue;
                }

                $assistanceCountToday = Assistance::query()
                    ->where('user_id', (int) $membershipUser->user_id)
                    ->whereMonth('created_at', $currentMonth)
                    ->whereYear('created_at', $currentYear)
                    ->count();

                $stats['assistance_records_today'] += $assistanceCountToday;

                if ($assistanceCountToday <= 0) {
                    $stats['skipped_no_assistance_today']++;
                    continue;
                }

                $existingSalesToday = Sale_details::query()
                    ->where('membership_user_id', (int) $membershipUser->id)
                    ->whereMonth('created_at', $currentMonth)
                    ->whereYear('created_at', $currentYear)
                    ->count();

                $stats['existing_sales_today'] += $existingSalesToday;

                if ($existingSalesToday >= $assistanceCountToday) {
                    $stats['skipped_already_synced']++;

                    if (empty($membershipUser->sale_id)) {
                        $existingSaleId = Sale_details::query()
                            ->where('membership_user_id', (int) $membershipUser->id)
                            ->whereMonth('created_at', $currentMonth)
                            ->whereYear('created_at', $currentYear)
                            ->latest('id')
                            ->value('sale_id');

                        if ($existingSaleId) {
                            $membershipUser->sale_id = (int) $existingSaleId;
                            $membershipUser->save();
                            $stats['linked_existing_sales']++;
                        }
                    }

                    continue;
                }

                $companyId = $this->getCompanyInfo((int) $membershipUser->user_id)?->id
                    ?? $defaultCompanyId;
                if (! $companyId) {
                    $stats['skipped_no_company']++;
                    Log::warning('Membresia diaria omitida por company_id no encontrado para el usuario.', [
                        'membership_user_id' => (int) $membershipUser->id,
                        'user_id' => (int) $membershipUser->user_id,
                    ]);
                    continue;
                }

                $peopleId = (int) ($membershipUser->user?->people_id ?? 0);
                if ($peopleId <= 0) {
                    $stats['skipped_no_people']++;
                    Log::warning('Membresia diaria omitida por people_id no encontrado.', [
                        'membership_user_id' => (int) $membershipUser->id,
                        'user_id' => (int) $membershipUser->user_id,
                    ]);
                    continue;
                }

                $paymentPlanName = $this->normalizePlanName($membershipUser->payment_plan?->plan_name);
                if (! $paymentPlanName && ! empty($membershipUser->payment_plan_id)) {
                    $paymentPlanName = $this->normalizePlanName(Payment_plan::query()
                        ->whereKey((int) $membershipUser->payment_plan_id)
                        ->value('plan_name'));
                }

                $baseTotal = round((float) ($membershipModel->price ?? 0), 2);
                $totalWithRate = round($baseTotal * $usdRate, 2);

                $missingSales = $assistanceCountToday - $existingSalesToday;
                $firstSaleId = null;

                for ($i = 0; $i < $missingSales; $i++) {
                    $saleDates = $saleScheduleService->calculateDatesForSegment($paymentPlanName, 1, now());
                    $saleIdentifier = $salesService->getNewSaleIdentifier((int) $companyId);

                    $sale = Sale::create([
                        'sale_identifier' => $saleIdentifier,
                        'people_id' => $peopleId,
                        'user_id' => (int) $membershipUser->user_id,
                        'company_id' => (int) $companyId,
                        'status' => 'pendiente',
                        'total' => $totalWithRate,
                        'debt' => $totalWithRate,
                        'aux_total' => $baseTotal,
                        'aux_debt' => $baseTotal,
                        'amount_rate' => $usdRate,
                        'expiration_date' => $saleDates['expiration_date'],
                        'alert_date' => $saleDates['alert_date'],
                    ]);

                    Sale_details::create([
                        'sale_id' => (int) $sale->id,
                        'product_id' => null,
                        'membership_user_id' => (int) $membershipUser->id,
                        'quantity' => 1,
                        'unit_price' => $totalWithRate,
                        'subtotal' => $totalWithRate,
                        'aux_unit_price' => $baseTotal,
                        'aux_subtotal' => $baseTotal,
                    ]);

                    if ($firstSaleId === null) {
                        $firstSaleId = (int) $sale->id;
                    }

                    $stats['created_sales']++;
                }

                if ($firstSaleId) {
                    $membershipUser->sale_id = $firstSaleId;
                    $membershipUser->save();
                }
            }

            DB::commit();
            return $stats;
        } catch (Throwable $exception) {
            DB::rollBack();
            Log::error('Error al actualizar ventas de membresias diarias por asistencia: ' . $exception->getMessage());
            throw $exception;
        }
    }
}
