<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\LoginHistory;
use App\Models\Order;
use App\Models\OrderRiskAssessment;
use App\Models\User;

class FraudDetectionService
{
    /**
     * Weight distribution for risk checks.
     *
     * @var array<string, int>
     */
    private const WEIGHTS = [
        'disposable_email' => 25,
        'velocity' => 20,
        'ip_country_mismatch' => 20,
        'email_domain_age' => 15,
        'previous_flags' => 20,
    ];

    public function assess(User $user, ?Order $order = null): OrderRiskAssessment
    {
        if (! config('fraud.enabled', true)) {
            return OrderRiskAssessment::create([
                'order_id' => $order?->id,
                'user_id' => $user->id,
                'risk_score' => 0,
                'risk_level' => 'low',
                'checks' => [],
                'auto_action' => 'approve',
            ]);
        }

        $ip = request()->ip() ?? '127.0.0.1';
        $billingCountry = $user->profile?->billing_country;

        $checks = [
            'disposable_email' => $this->checkDisposableEmail($user->email),
            'velocity' => $this->checkVelocity($user),
            'ip_country_mismatch' => $this->checkIpCountryMismatch($ip, $billingCountry),
            'email_domain_age' => $this->checkEmailDomainAge($user->email),
            'previous_flags' => $this->checkPreviousFraudFlags($user),
        ];

        $totalScore = 0;
        foreach ($checks as $name => $check) {
            $weight = self::WEIGHTS[$name] ?? 0;
            $totalScore += (int) round($check['score'] * ($weight / 100));
        }

        $totalScore = min(100, max(0, $totalScore));

        $thresholds = config('fraud.thresholds', []);
        $autoApproveBelow = (int) ($thresholds['auto_approve_below'] ?? 30);
        $autoHoldAbove = (int) ($thresholds['auto_hold_above'] ?? 60);
        $autoRejectAbove = (int) ($thresholds['auto_reject_above'] ?? 85);

        $autoAction = match (true) {
            $totalScore >= $autoRejectAbove => 'reject',
            $totalScore >= $autoHoldAbove => 'hold',
            default => 'approve',
        };

        $riskLevel = match (true) {
            $totalScore >= $autoRejectAbove => 'critical',
            $totalScore >= $autoHoldAbove => 'high',
            $totalScore >= $autoApproveBelow => 'medium',
            default => 'low',
        };

        return OrderRiskAssessment::create([
            'order_id' => $order?->id,
            'user_id' => $user->id,
            'risk_score' => $totalScore,
            'risk_level' => $riskLevel,
            'checks' => $checks,
            'auto_action' => $autoAction,
        ]);
    }

    /**
     * Check if the email domain is a known disposable email provider.
     *
     * @return array{score: int, details: string}
     */
    public function checkDisposableEmail(string $email): array
    {
        $domain = strtolower(substr($email, strrpos($email, '@') + 1));
        $disposableDomains = config('fraud.disposable_email_domains', []);

        if (in_array($domain, $disposableDomains, true)) {
            return [
                'score' => 100,
                'details' => "Disposable email domain detected: {$domain}",
            ];
        }

        return [
            'score' => 0,
            'details' => 'Email domain is legitimate',
        ];
    }

    /**
     * Check signup velocity from the same IP in the last 24 hours.
     *
     * @return array{score: int, details: string}
     */
    public function checkVelocity(User $user): array
    {
        $ip = request()->ip() ?? '127.0.0.1';

        $recentSignups = LoginHistory::where('ip_address', $ip)
            ->where('created_at', '>=', now()->subDay())
            ->where('success', true)
            ->distinct('user_id')
            ->count('user_id');

        if ($recentSignups >= 5) {
            return [
                'score' => 100,
                'details' => "{$recentSignups} distinct user logins from IP {$ip} in 24h",
            ];
        }

        if ($recentSignups >= 3) {
            return [
                'score' => 60,
                'details' => "{$recentSignups} distinct user logins from IP {$ip} in 24h",
            ];
        }

        return [
            'score' => 0,
            'details' => 'Normal signup velocity',
        ];
    }

    /**
     * Check if the IP geolocation matches the billing country.
     *
     * @return array{score: int, details: string}
     */
    public function checkIpCountryMismatch(string $ip, ?string $billingCountry): array
    {
        if ($billingCountry === null) {
            return [
                'score' => 10,
                'details' => 'No billing country set for comparison',
            ];
        }

        try {
            $location = geoip($ip);
            $ipCountry = $location->iso_code ?? null;

            if ($ipCountry === null) {
                return [
                    'score' => 10,
                    'details' => 'Unable to determine IP country',
                ];
            }

            if (strtoupper($ipCountry) !== strtoupper($billingCountry)) {
                return [
                    'score' => 80,
                    'details' => "IP country ({$ipCountry}) does not match billing country ({$billingCountry})",
                ];
            }

            return [
                'score' => 0,
                'details' => 'IP country matches billing country',
            ];
        } catch (\Throwable) {
            return [
                'score' => 0,
                'details' => 'GeoIP lookup unavailable, skipping check',
            ];
        }
    }

    /**
     * Check if the email domain has valid MX records.
     *
     * @return array{score: int, details: string}
     */
    public function checkEmailDomainAge(string $email): array
    {
        $domain = substr($email, strrpos($email, '@') + 1);

        try {
            $mxRecords = [];
            $hasMx = @getmxrr($domain, $mxRecords);

            if (! $hasMx || empty($mxRecords)) {
                return [
                    'score' => 80,
                    'details' => "No MX records found for domain: {$domain}",
                ];
            }

            return [
                'score' => 0,
                'details' => "Email domain {$domain} has valid MX records",
            ];
        } catch (\Throwable) {
            return [
                'score' => 0,
                'details' => 'DNS lookup unavailable, skipping check',
            ];
        }
    }

    /**
     * Check if the user has previous fraud flags or risk assessments.
     *
     * @return array{score: int, details: string}
     */
    public function checkPreviousFraudFlags(User $user): array
    {
        $previousFlags = OrderRiskAssessment::where('user_id', $user->id)
            ->whereIn('risk_level', ['high', 'critical'])
            ->count();

        if ($previousFlags >= 3) {
            return [
                'score' => 100,
                'details' => "{$previousFlags} previous high/critical risk assessments",
            ];
        }

        if ($previousFlags >= 1) {
            return [
                'score' => 50,
                'details' => "{$previousFlags} previous high/critical risk assessment(s)",
            ];
        }

        return [
            'score' => 0,
            'details' => 'No previous fraud flags',
        ];
    }
}
