<?php

namespace App\Services\Fraud;

use App\Jobs\RunFraudChecksJob;
use App\Models\FraudModuleSetting;
use App\Models\Order;
use App\Models\OrderFraudCheck;
use App\Services\Fraud\Contracts\FraudCheckInterface;
use App\Services\Fraud\Checks\VelocityCheck;
use App\Services\Fraud\Checks\IpReputationCheck;
use App\Services\Fraud\Checks\DeviceFingerprintCheck;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Fraud Detection Service
 *
 * Sipariş fraud kontrolü yapan ana servis
 */
class FraudDetectionService
{
    /**
     * Registered fraud check modules
     */
    protected array $checks = [];

    /**
     * Fraud status thresholds
     */
    protected int $approvedThreshold = 30;      // 0-30: Approved
    protected int $reviewThreshold = 70;        // 31-70: Review
                                                // 71-100: Rejected

    public function __construct()
    {
        $this->registerDefaultChecks();
    }

    /**
     * Register default fraud check modules
     *
     * Reads configuration from Core fraud module (fraud_module_settings table).
     * Falls back to Settings table values if Core module is not found.
     */
    protected function registerDefaultChecks(): void
    {
        $coreConfig = $this->getCoreModuleConfig();

        // If Core module exists and is disabled, skip internal checks entirely
        if ($coreConfig !== null && empty($coreConfig['enabled'])) {
            return;
        }

        $velocityCheck = new VelocityCheck();
        $ipReputationCheck = new IpReputationCheck();
        $deviceFingerprintCheck = new DeviceFingerprintCheck();

        // Pass Core module config to check classes if available
        if ($coreConfig !== null) {
            $velocityCheck->setConfig($coreConfig);
            $ipReputationCheck->setConfig($coreConfig);
            $deviceFingerprintCheck->setConfig($coreConfig);
        }

        $this->registerCheck($velocityCheck);
        $this->registerCheck($ipReputationCheck);
        $this->registerCheck($deviceFingerprintCheck);
    }

    /**
     * Get Core fraud module configuration from database
     *
     * @return array|null Config array, or null if Core module not found in DB
     */
    protected function getCoreModuleConfig(): ?array
    {
        try {
            $setting = FraudModuleSetting::where('module_slug', 'core')->first();

            if (!$setting) {
                return null;
            }

            return array_merge(
                ['enabled' => $setting->enabled],
                $setting->config ?? []
            );
        } catch (\Exception $e) {
            // Table might not exist yet (during migrations)
            return null;
        }
    }

    /**
     * Register a fraud check module
     */
    public function registerCheck(FraudCheckInterface $check): void
    {
        if ($check->isEnabled()) {
            $this->checks[] = $check;
        }
    }

    /**
     * Get all registered checks
     */
    public function getChecks(): array
    {
        // Priority'ye göre sırala
        usort($this->checks, function ($a, $b) {
            return $a->getPriority() <=> $b->getPriority();
        });

        return $this->checks;
    }

    /**
     * Run fraud detection on order
     *
     * @param Order $order
     * @param bool $async Run checks asynchronously (recommended for production)
     * @return array
     */
    public function analyze(Order $order, bool $async = false): array
    {
        if ($async) {
            RunFraudChecksJob::dispatch($order);

            $order->updateQuietly(['fraud_status' => 'checking']);

            return [
                'fraud_status' => 'checking',
                'fraud_score' => 0,
                'message' => 'Fraud checks queued for processing',
            ];
        }

        return $this->runChecks($order);
    }

    /**
     * Run all fraud checks on order
     */
    public function runChecks(Order $order): array
    {
        $startTime = microtime(true);
        $totalRiskScore = 0;
        $failedChecks = [];
        $warningChecks = [];
        $checksRun = 0;

        // PRC-622 — WHMCS-vari fraud hook'ları (hook-only; bildirim eşlemesi
        // yok). PreFraudCheck = kontroller başlamadan önce, RunFraudCheck =
        // kontroller çalıştırılırken.
        $bridge = app(\App\Core\Hook\HookBridge::class);
        $bridge->fireAction('PreFraudCheck', $bridge->buildOrderContext($order));
        $bridge->fireAction('RunFraudCheck', $bridge->buildOrderContext($order));

        DB::beginTransaction();

        try {
            // Run internal fraud checks
            foreach ($this->getChecks() as $check) {
                $checkStartTime = microtime(true);

                try {
                    $result = $check->check($order);
                    $executionTime = (int) ((microtime(true) - $checkStartTime) * 1000);

                    // Save check result
                    $fraudCheck = OrderFraudCheck::create([
                        'order_id' => $order->id,
                        'check_type' => $check->getType(),
                        'check_status' => $result['status'],
                        'risk_score' => $result['risk_score'],
                        'reason' => $result['reason'],
                        'check_data' => $result['data'] ?? [],
                        'metadata' => [
                            'check_name' => $check->getName(),
                            'check_description' => $check->getDescription(),
                        ],
                        'execution_time_ms' => $executionTime,
                        'checked_at' => now(),
                    ]);

                    $checksRun++;

                    // Accumulate risk score (only from failed/warning checks)
                    if ($result['status'] === 'failed' || $result['status'] === 'warning') {
                        $totalRiskScore += $result['risk_score'];

                        if ($result['status'] === 'failed') {
                            $failedChecks[] = [
                                'check' => $check->getName(),
                                'reason' => $result['reason'],
                                'risk_score' => $result['risk_score'],
                            ];
                        } else {
                            $warningChecks[] = [
                                'check' => $check->getName(),
                                'reason' => $result['reason'],
                                'risk_score' => $result['risk_score'],
                            ];
                        }
                    }

                } catch (\Exception $e) {
                    Log::error('Fraud check failed: ' . $check->getName(), [
                        'order_id' => $order->id,
                        'error' => $e->getMessage(),
                    ]);

                    // Log failed check
                    OrderFraudCheck::create([
                        'order_id' => $order->id,
                        'check_type' => $check->getType(),
                        'check_status' => 'skipped',
                        'risk_score' => 0,
                        'reason' => 'Check failed: ' . $e->getMessage(),
                        'check_data' => [],
                        'checked_at' => now(),
                    ]);
                }
            }

            // Run external fraud modules (MaxMind, FraudLabs, etc.)
            $moduleManager = app(FraudModuleManager::class);
            $enabledModules = $moduleManager->getEnabledModules();

            foreach ($enabledModules as $moduleData) {
                $checkStartTime = microtime(true);
                $module = $moduleData['module'];
                $config = $moduleData['config'];

                try {
                    // Run module check
                    $response = $module->check($order, $config);

                    // Validate rules (may throw exception if order should be rejected)
                    try {
                        $module->validateRules($config, $response);
                        $checkStatus = 'passed';
                        $reason = 'All fraud rules passed';
                        $riskScore = 0;
                    } catch (\Exception $validationError) {
                        $checkStatus = 'failed';
                        $reason = $validationError->getMessage();
                        $riskScore = (int) $response->get('risk_score', 50);
                    }

                    $executionTime = (int) ((microtime(true) - $checkStartTime) * 1000);

                    // Save check result
                    OrderFraudCheck::create([
                        'order_id' => $order->id,
                        'check_type' => 'external_' . $module->getMetadata()['slug'],
                        'check_status' => $checkStatus,
                        'risk_score' => $riskScore,
                        'reason' => $reason,
                        'check_data' => $response->toArray(),
                        'metadata' => [
                            'check_name' => $module->getMetadata()['name'],
                            'check_description' => $module->getMetadata()['description'],
                            'module_type' => 'external',
                        ],
                        'execution_time_ms' => $executionTime,
                        'checked_at' => now(),
                    ]);

                    $checksRun++;

                    // Accumulate risk score
                    if ($checkStatus === 'failed') {
                        $totalRiskScore += $riskScore;
                        $failedChecks[] = [
                            'check' => $module->getMetadata()['name'],
                            'reason' => $reason,
                            'risk_score' => $riskScore,
                        ];
                    }

                } catch (\Exception $e) {
                    Log::error('External fraud module failed: ' . $module->getMetadata()['name'], [
                        'order_id' => $order->id,
                        'error' => $e->getMessage(),
                    ]);

                    // Log failed module check
                    OrderFraudCheck::create([
                        'order_id' => $order->id,
                        'check_type' => 'external_' . $module->getMetadata()['slug'],
                        'check_status' => 'skipped',
                        'risk_score' => 0,
                        'reason' => 'Module failed: ' . $e->getMessage(),
                        'check_data' => [],
                        'metadata' => [
                            'check_name' => $module->getMetadata()['name'],
                            'module_type' => 'external',
                        ],
                        'checked_at' => now(),
                    ]);
                }
            }

            // Calculate final fraud score (capped at 100)
            $finalFraudScore = min(100, $totalRiskScore);

            // Determine fraud status
            $fraudStatus = $this->determineFraudStatus($finalFraudScore, $failedChecks);

            // Update order
            $order->update([
                'fraud_status' => $fraudStatus,
                'fraud_score' => $finalFraudScore,
                'fraud_checks' => [
                    'total_checks' => $checksRun,
                    'failed_checks' => count($failedChecks),
                    'warning_checks' => count($warningChecks),
                    'checked_at' => now()->toIso8601String(),
                ],
            ]);

            DB::commit();

            // PRC-622 — sonuç hook'ları (commit SONRASI). 'Fraud Order Flagged'
            // admin maili RunFraudChecksJob::notifyAdmin'de ZATEN gidiyor →
            // 'FraudOrder' için communication_events eşlemesi bilinçli
            // EKLENMEDİ (çift bildirim olurdu); hepsi hook-only.
            $resultCtx = $bridge->buildOrderContext($order, [
                'fraud_status' => $fraudStatus,
                'fraud_score' => $finalFraudScore,
            ]);
            match ($fraudStatus) {
                'approved' => $bridge->fireAction('FraudCheckPassed', $resultCtx),
                'review' => $bridge->fireAction('FraudCheckAwaitingUserInput', $resultCtx),
                'rejected' => (function () use ($bridge, $resultCtx) {
                    $bridge->fireAction('FraudCheckFailed', $resultCtx);
                    $bridge->fireAction('FraudOrder', $resultCtx);
                })(),
                default => null,
            };

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            return [
                'fraud_status' => $fraudStatus,
                'fraud_score' => $finalFraudScore,
                'checks_run' => $checksRun,
                'failed_checks' => $failedChecks,
                'warning_checks' => $warningChecks,
                'execution_time_ms' => $executionTime,
                'message' => $this->getFraudStatusMessage($fraudStatus, $finalFraudScore),
            ];

        } catch (\Exception $e) {
            DB::rollBack();

            Log::error('Fraud detection failed', [
                'order_id' => $order->id,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }

    /**
     * Determine fraud status based on score and failed checks
     */
    protected function determineFraudStatus(int $fraudScore, array $failedChecks): string
    {
        // Eğer critical fail varsa direkt reject
        foreach ($failedChecks as $check) {
            if ($check['risk_score'] >= 80) {
                return 'rejected';
            }
        }

        // Score bazlı karar
        if ($fraudScore >= $this->reviewThreshold) {
            return count($failedChecks) > 0 ? 'rejected' : 'review';
        } elseif ($fraudScore >= $this->approvedThreshold) {
            return 'review';
        }

        return 'approved';
    }

    /**
     * Get fraud status message
     */
    protected function getFraudStatusMessage(string $status, int $score): string
    {
        return match($status) {
            'approved' => "Order approved - Low fraud risk (Score: {$score})",
            'review' => "Order needs review - Medium fraud risk (Score: {$score})",
            'rejected' => "Order rejected - High fraud risk (Score: {$score})",
            'checking' => 'Fraud checks in progress',
            default => 'Unknown fraud status',
        };
    }

    /**
     * Re-check order fraud status
     */
    public function recheck(Order $order): array
    {
        // Delete old checks
        OrderFraudCheck::where('order_id', $order->id)->delete();

        // Run checks again
        return $this->runChecks($order);
    }

    /**
     * Get fraud summary for order
     */
    public function getSummary(Order $order): array
    {
        $checks = OrderFraudCheck::where('order_id', $order->id)
            ->orderBy('checked_at', 'desc')
            ->get();

        return [
            'fraud_status' => $order->fraud_status,
            'fraud_score' => $order->fraud_score,
            'total_checks' => $checks->count(),
            'passed_checks' => $checks->where('check_status', 'passed')->count(),
            'failed_checks' => $checks->where('check_status', 'failed')->count(),
            'warning_checks' => $checks->where('check_status', 'warning')->count(),
            'skipped_checks' => $checks->where('check_status', 'skipped')->count(),
            'checks' => $checks->map(function ($check) {
                return [
                    'type' => $check->check_type,
                    'status' => $check->check_status,
                    'risk_score' => $check->risk_score,
                    'reason' => $check->reason,
                    'checked_at' => $check->checked_at->format('Y-m-d H:i:s'),
                ];
            }),
        ];
    }
}
