<?php

namespace MtcPharmacy\ReferAFriend\Classes;

use Illuminate\Support\Facades\DB;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Coupon;
use MtcPharmacy\ReferAFriend\Models\Referral;
use MtcPharmacy\ReferAFriend\Models\ReferralReward;
use Mtc\Core\Currency;
use MtcPharmacy\ReferAFriend\Models\ReferralActivityLog;

class ReferralService
{
    public function createReferral(Member $referrer, string $email, string $channel = 'email'): Referral
    {
        $email = strtolower(trim($email));

        // Validate not self-referral
        if ($email === strtolower($referrer->email)) {
            throw new \InvalidArgumentException('You cannot refer yourself.');
        }

        // Validate email not already a registered member (silent rejection for privacy)
        if (Member::where('email', $email)->exists()) {
            // Return a "fake" success to protect privacy — don't reveal member exists
            $referral = Referral::create([
                'referrer_id'   => $referrer->id,
                'referee_email' => $email,
                'referral_code' => Referral::generateCode(),
                'channel'       => $channel,
                'status'        => 'expired', // Immediately expired so it's never processed
                'expires_at'    => now(),
            ]);

            $this->logActivity($referral->id, $referrer->id, 'invite_sent', [
                'email' => $email,
                'channel' => $channel,
                'note' => 'existing_member',
            ]);

            return $referral;
        }

        // Check max referrals per member
        $activeCount = Referral::byReferrer($referrer->id)->active()->count();
        if ($activeCount >= config('referrals.max_referrals_per_member')) {
            throw new \InvalidArgumentException('You have reached the maximum number of referrals.');
        }

        // Check no existing active referral for this email from this referrer
        $existingReferral = Referral::byReferrer($referrer->id)
            ->byEmail($email)
            ->active()
            ->first();

        if ($existingReferral) {
            throw new \InvalidArgumentException('You have already invited this email address.');
        }

        // Create referral
        $referral = Referral::create([
            'referrer_id'   => $referrer->id,
            'referee_email' => $email,
            'referral_code' => Referral::generateCode(),
            'channel'       => $channel,
            'status'        => 'pending',
            'expires_at'    => now()->addDays(config('referrals.link_expiry_days')),
        ]);

        // Queue invitation email
        $this->sendInvitationEmail($referral, $referrer);

        // Log activity
        $this->logActivity($referral->id, $referrer->id, 'invite_sent', [
            'email' => $email,
            'channel' => $channel,
        ]);

        return $referral;
    }

    public function processNewMember(Member $member): void
    {
        // Check session first, then cookie for referral code
        $referralCode = session('referral_code');
        if (!$referralCode) {
            $referralCode = request()->cookie(config('referrals.fraud.cookie_name'));
        }

        if (!$referralCode) {
            return;
        }

        // Look up pending, non-expired referral
        $referral = Referral::where('referral_code', $referralCode)
            ->pending()
            ->where(function ($query) {
                $query->whereNull('expires_at')
                    ->orWhere('expires_at', '>', now());
            })
            ->first();

        if (!$referral) {
            return;
        }

        // Run fraud checks
        $flags = [];
        if (class_exists(ReferralFraudChecker::class)) {
            $fraudChecker = new ReferralFraudChecker();
            $flags = $fraudChecker->checkAll($referral, $member);

            foreach ($flags as $flag) {
                $this->logActivity($referral->id, $member->id, 'fraud_flagged', [
                    'flag' => $flag,
                ]);
            }
        }

        // If fraud flags detected, set status to fraud_flagged to prevent auto-reward
        $status = !empty($flags) ? 'fraud_flagged' : 'signed_up';

        // Update referral
        $referral->update([
            'referee_id'     => $member->id,
            'referee_email'  => strtolower(trim($member->email)),
            'referee_ip_hash' => Referral::hashIp(request()->ip()),
            'referee_cookie' => request()->cookie(config('referrals.fraud.device_cookie_name')),
            'status'         => $status,
        ]);

        // Set referrer_member_id on the new member
        $member->referrer_member_id = $referral->referrer_id;
        $member->save();

        // Log activity
        $this->logActivity($referral->id, $member->id, 'signed_up');

        // Clear session referral code
        session()->forget('referral_code');
    }

    public function processShippedOrder($order): void
    {
        $memberId = $order->member ?? null;
        if (!$memberId) {
            return;
        }

        // Find referral where referee_id matches and status is eligible
        $referral = Referral::where('referee_id', $memberId)
            ->whereIn('status', ['signed_up', 'order_placed'])
            ->first();

        if (!$referral) {
            return;
        }

        // Check order total meets minimum
        $orderTotal = (float) (method_exists($order, 'getTotalCost') ? $order->getTotalCost() : ($order->cost_total ?? 0));
        if ($orderTotal < config('referrals.min_qualifying_order')) {
            return;
        }

        // Use DB transaction + lock to prevent double-issue
        DB::transaction(function () use ($referral, $order, $memberId) {
            $referral = Referral::where('id', $referral->id)
                ->lockForUpdate()
                ->first();

            if (!$referral || !in_array($referral->status, ['signed_up', 'order_placed'], true)) {
                return;
            }

            $orderId = $order->id ?? $order->getId();

            $referral->update([
                'status'              => 'shipped',
                'qualifying_order_id' => $orderId,
            ]);

            $this->logActivity($referral->id, $memberId, 'order_shipped', [
                'order_id' => $orderId,
            ]);

            // Issue reward to referrer (if account is active). Any exception
            // bubbles up and the surrounding transaction rolls back every
            // write made in this block, including coupons created inside
            // issueReward — so explicit cleanup here would be undone.
            $referrer = Member::find($referral->referrer_id);
            if ($referrer && $referrer->is_activated) {
                $this->issueReward($referral, $referrer, 'referrer');
            }

            $referee = Member::find($referral->referee_id);
            if ($referee) {
                $this->issueReward($referral, $referee, 'referee');
            }

            $referral->update(['status' => 'rewarded']);
        });
    }

    public function issueReward(Referral $referral, Member $member, string $type): ReferralReward
    {
        // Generate coupon via existing system
        $coupon = Coupon::generateCoupon([
            'prefix'      => config('referrals.coupon_prefix'),
            'type'        => 'single',
            'value'       => config('referrals.reward_amount'),
            'min_price'   => config('referrals.coupon_min_spend'),
            'redemptions' => 1,
            'name'        => 'Refer a Friend Reward',
            'exp_date'    => now()->addDays(config('referrals.coupon_expiry_days'))->format('Y-m-d'),
        ]);

        // Create reward record
        $reward = ReferralReward::create([
            'referral_id' => $referral->id,
            'member_id'   => $member->id,
            'type'        => $type,
            'coupon_id'   => $coupon->id,
            'coupon_code' => $coupon->code,
            'amount'      => config('referrals.reward_amount'),
            'status'      => 'issued',
            'issued_at'   => now(),
        ]);

        // Queue reward notification email
        $this->sendRewardEmail($reward, $member, $coupon);

        // Log activity
        $this->logActivity($referral->id, $member->id, 'reward_issued', [
            'type'        => $type,
            'coupon_code' => $coupon->code,
            'amount'      => config('referrals.reward_amount'),
        ]);

        return $reward;
    }

    public function handleOrderCancellation($order): void
    {
        $orderId = $order->id ?? $order->getId();

        $referral = Referral::where('qualifying_order_id', $orderId)->first();
        if (!$referral) {
            return;
        }

        $rewards = $referral->rewards()->whereIn('status', ['pending', 'issued'])->get();

        foreach ($rewards as $reward) {
            // Set coupon redemptions to 0 to invalidate
            if ($reward->coupon_id) {
                $coupon = Coupon::find($reward->coupon_id);
                if ($coupon) {
                    $coupon->redemptions = 0;
                    $coupon->save();
                }
            }

            $reward->update([
                'status'        => 'revoked',
                'revoked_at'    => now(),
                'revoke_reason' => 'Qualifying order cancelled/refunded',
            ]);

            $this->logActivity($referral->id, $reward->member_id, 'reward_revoked', [
                'reason' => 'order_cancellation',
                'order_id' => $orderId,
            ]);
        }

        // Check if any rewards were already redeemed
        $redeemedRewards = $referral->rewards()->where('status', 'redeemed')->exists();

        $referral->update([
            'status' => 'cancelled',
        ]);

        if ($redeemedRewards) {
            $this->logActivity($referral->id, null, 'fraud_flagged', [
                'flag' => 'redeemed_rewards_on_cancelled_order',
                'order_id' => $orderId,
            ]);
        }
    }

    public function sendReminder(Referral $referral): void
    {
        // Re-send invitation email
        $referrer = Member::find($referral->referrer_id);
        if ($referrer) {
            $this->sendInvitationEmail($referral, $referrer);
        }

        $referral->update(['reminder_sent_at' => now()]);

        $this->logActivity($referral->id, $referral->referrer_id, 'reminder_sent');
    }

    // --- Protected helpers ---

    protected function sendInvitationEmail(Referral $referral, Member $referrer): void
    {
        $referralUrl = config('app.url') . '/refer-a-friend/' . $referral->referral_code;

        $emailContent = template('emails/referral_invite.twig', [
            'referrer_name' => $referrer->fullname ?? 'Your friend',
            'referral_url'  => $referralUrl,
            'referral_code' => $referral->referral_code,
            'reward_amount' => config('referrals.reward_amount'),
            'min_order'     => config('referrals.min_qualifying_order'),
            'email_title'   => ($referrer->fullname ?? 'Your friend') . ' thinks you\'d love ' . config('app.name'),
        ]);

        email(
            $referral->referee_email,
            ($referrer->fullname ?? 'Your friend') . ' thinks you\'d love ' . config('app.name'),
            $emailContent,
            ['queue' => true]
        );
    }

    protected function sendRewardEmail(ReferralReward $reward, Member $member, $coupon): void
    {
        $emailContent = template('emails/referral_reward_earned.twig', [
            'member_name'  => $member->fullname ?? 'there',
            'coupon_code'  => $coupon->code,
            'amount'       => config('referrals.reward_amount'),
            'min_spend'    => config('referrals.coupon_min_spend'),
            'expiry_date'  => $coupon->exp_date,
            'email_title'  => 'You\'ve earned a ' . $this->formatCurrency(config('referrals.reward_amount')) . ' reward!',
        ]);

        email(
            $member->email,
            'You\'ve earned a ' . $this->formatCurrency(config('referrals.reward_amount')) . ' reward!',
            $emailContent,
            ['queue' => true]
        );
    }

    protected function formatCurrency(float $amount): string
    {
        $currency = $_SESSION['currency'] ?? Currency::getCurrencyFromCode();

        return html_entity_decode($currency['symbol']) . number_format($amount * $currency['ratio'], 2);
    }

    protected function logActivity(?int $referralId, ?int $memberId, string $action, ?array $details = null): void
    {
        ReferralActivityLog::create([
            'referral_id' => $referralId,
            'member_id'   => $memberId,
            'action'      => $action,
            'details'     => $details ? json_encode($details) : null,
            'ip_address'  => request()->ip(),
            'created_at'  => now(),
        ]);
    }
}
