<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Affiliate;
use App\Models\AffiliateCommission;
use App\Models\AffiliatePayout;
use App\Models\AffiliateReferral;
use App\Models\PaymentTransaction;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class AffiliateService
{
    public function apply(User $user): Affiliate
    {
        return Affiliate::create([
            'user_id' => $user->id,
            'referral_code' => Affiliate::generateReferralCode(),
            'status' => 'pending',
            'commission_type' => config('affiliate.default_commission_type', 'percentage'),
            'commission_rate' => config('affiliate.default_commission_rate', 10.00),
            'recurring_commissions' => config('affiliate.default_recurring_commissions', false),
            'minimum_payout' => config('affiliate.default_minimum_payout', 50.00),
        ]);
    }

    public function approve(Affiliate $affiliate): void
    {
        $affiliate->update([
            'status' => 'active',
            'approved_at' => now(),
        ]);
    }

    public function recordReferral(
        Affiliate $affiliate,
        User $referredUser,
        ?string $ip = null,
        ?string $url = null,
    ): AffiliateReferral {
        return AffiliateReferral::create([
            'affiliate_id' => $affiliate->id,
            'referred_user_id' => $referredUser->id,
            'status' => 'pending',
            'signup_ip' => $ip,
            'referral_url' => $url,
        ]);
    }

    public function recordCommission(
        AffiliateReferral $referral,
        PaymentTransaction $transaction,
        string $type,
    ): AffiliateCommission {
        $affiliate = $referral->affiliate;

        $amount = $affiliate->commission_type === 'percentage'
            ? round((float) $transaction->amount * ((float) $affiliate->commission_rate / 100), 2)
            : (float) $affiliate->commission_rate;

        $commission = AffiliateCommission::create([
            'affiliate_id' => $affiliate->id,
            'referral_id' => $referral->id,
            'payment_transaction_id' => $transaction->id,
            'amount' => $amount,
            'currency' => $transaction->currency ?? 'USD',
            'type' => $type,
            'status' => 'pending',
        ]);

        $affiliate->increment('pending_balance', $amount);

        return $commission;
    }

    public function approveCommission(AffiliateCommission $commission): void
    {
        DB::transaction(function () use ($commission): void {
            $commission->update([
                'status' => 'approved',
                'approved_at' => now(),
            ]);

            $affiliate = $commission->affiliate;
            $affiliate->increment('total_earned', (float) $commission->amount);
        });
    }

    public function requestPayout(Affiliate $affiliate, string $method): AffiliatePayout
    {
        $amount = (float) $affiliate->pending_balance;

        if ($amount < (float) $affiliate->minimum_payout) {
            throw new \InvalidArgumentException(
                "Pending balance ({$amount}) is below minimum payout threshold ({$affiliate->minimum_payout})."
            );
        }

        return DB::transaction(function () use ($affiliate, $method, $amount): AffiliatePayout {
            $payout = AffiliatePayout::create([
                'affiliate_id' => $affiliate->id,
                'amount' => $amount,
                'currency' => 'USD',
                'method' => $method,
                'status' => 'pending',
            ]);

            $affiliate->decrement('pending_balance', $amount);

            return $payout;
        });
    }

    public function processPayout(AffiliatePayout $payout): void
    {
        DB::transaction(function () use ($payout): void {
            $payout->update([
                'status' => 'completed',
                'processed_at' => now(),
                'reference' => 'PAY-'.strtoupper(bin2hex(random_bytes(6))),
            ]);

            $affiliate = $payout->affiliate;
            $affiliate->increment('total_paid', (float) $payout->amount);

            // Mark associated pending commissions as paid
            $affiliate->commissions()
                ->where('status', 'approved')
                ->update([
                    'status' => 'paid',
                    'paid_at' => now(),
                ]);
        });
    }
}
