<?php

namespace App\Nexus\Engine;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * NEXUS ENGINE - NexusAuditLogger
 *
 * Single class responsible for writing every entry to nexus_audit_log.
 * All methods are static for easy use anywhere.
 * Every method wraps writes in try/catch -- a logging failure never crashes
 * the main operation.
 *
 * DEPLOYED TABLE COLUMNS (nexus_audit_log):
 *   id, event, actor_type, actor_id, subject_user_id, nexus_session_id,
 *   sell_id, bid_id, context (JSON), notes, created_at
 *
 * PUBLIC API IS UNCHANGED from what engine classes call.
 * Internal mapping handles the column translation.
 *
 * @package App\Nexus\Engine
 */
class NexusAuditLogger
{
    /**
     * Log an action taken by an admin through the NEXUS dashboard.
     * Use for: config changes, plan range updates, emergency pause, login events.
     *
     * @param int      $adminId    admins.id of the admin
     * @param string   $actionType e.g. 'config_changed', 'emergency_pause'
     * @param string   $entityType e.g. 'config', 'session', 'plan', 'system'
     * @param int|null $entityId   ID of the affected record
     * @param string   $reason     Human-readable description
     * @param array    $data       Optional extra data
     */
    public static function logAdminAction(
        int $adminId,
        string $actionType,
        string $entityType,
        ?int $entityId,
        string $reason,
        array $data = []
    ): void {
        try {
            DB::table('nexus_audit_log')->insert([
                'event'            => $actionType,
                'actor_type'       => 'admin',
                'actor_id'         => $adminId,
                'subject_user_id'  => null,
                'nexus_session_id' => null,
                'sell_id'          => null,
                'bid_id'           => null,
                'context'          => self::encodeContext(array_merge($data, [
                    'entity_type' => $entityType,
                    'entity_id'   => $entityId,
                ])),
                'notes'            => $reason,
                'created_at'       => now(),
            ]);
        } catch (\Throwable $e) {
            Log::error('[NexusAuditLogger] Failed to write admin action to DB', [
                'error'       => $e->getMessage(),
                'admin_id'    => $adminId,
                'action_type' => $actionType,
                'reason'      => $reason,
            ]);
        }
    }

    /**
     * Log a decision made by the NEXUS algorithm (automated).
     * Use for: bid evaluations, sell evaluations, session events.
     *
     * @param int|null $sessionId  nexus_sessions.id
     * @param int|null $userId     users.id of the affected user
     * @param string   $actionType e.g. 'bid_evaluated', 'sell_evaluated'
     * @param string   $entityType e.g. 'bid', 'sell', 'session'
     * @param int|null $entityId   ID of the affected record
     * @param string   $decision   'approved', 'rejected', or 'guaranteed'
     * @param string   $reason     Why this decision was made
     * @param array    $scoreData  Full scoring breakdown for auditability
     */
    public static function logAlgorithmDecision(
        ?int $sessionId,
        ?int $userId,
        string $actionType,
        string $entityType,
        ?int $entityId,
        string $decision,
        string $reason,
        array $scoreData = []
    ): void {
        try {
            // Map entityId to the correct FK column based on type
            $sellId = ($entityType === 'sell') ? $entityId : null;
            $bidId  = ($entityType === 'bid') ? $entityId : null;

            DB::table('nexus_audit_log')->insert([
                'event'            => $actionType,
                'actor_type'       => 'engine',
                'actor_id'         => null,
                'subject_user_id'  => $userId,
                'nexus_session_id' => $sessionId,
                'sell_id'          => $sellId,
                'bid_id'           => $bidId,
                'context'          => self::encodeContext(array_merge($scoreData, [
                    'decision'    => $decision,
                    'entity_type' => $entityType,
                ])),
                'notes'            => $reason,
                'created_at'       => now(),
            ]);
        } catch (\Throwable $e) {
            Log::error('[NexusAuditLogger] Failed to write algorithm decision to DB', [
                'error'      => $e->getMessage(),
                'session_id' => $sessionId,
                'user_id'    => $userId,
                'action'     => $actionType,
                'decision'   => $decision,
                'reason'     => $reason,
            ]);
        }
    }

    /**
     * Log a login attempt (successful or failed).
     *
     * @param string   $email   The email used
     * @param string   $ip      The IP address
     * @param bool     $success Whether login succeeded
     * @param int|null $adminId The admin ID if successful
     */
    public static function logLoginAttempt(
        string $email,
        string $ip,
        bool $success,
        ?int $adminId = null
    ): void {
        $event = $success ? 'admin_login' : 'admin_login_failed';
        $message = ($success ? 'Successful login' : 'Failed login attempt') . " from {$ip} for {$email}";

        try {
            DB::table('nexus_audit_log')->insert([
                'event'            => $event,
                'actor_type'       => 'admin',
                'actor_id'         => $adminId,
                'subject_user_id'  => null,
                'nexus_session_id' => null,
                'sell_id'          => null,
                'bid_id'           => null,
                'context'          => self::encodeContext(['email' => $email, 'ip' => $ip]),
                'notes'            => $message,
                'created_at'       => now(),
            ]);
        } catch (\Throwable $e) {
            if (!str_contains($e->getMessage(), "doesn't exist") && !str_contains($e->getMessage(), 'does not exist')) {
                Log::error('[NexusAuditLogger] Failed to write login attempt to DB', [
                    'error'   => $e->getMessage(),
                    'email'   => $email,
                    'ip'      => $ip,
                    'success' => $success,
                ]);
            }
        }
    }

    /**
     * Log a session lifecycle event (opened, closed, processed).
     *
     * @param int    $sessionId nexus_sessions.id
     * @param string $event     'session_opened', 'session_closed', 'session_processed'
     * @param array  $data      Optional extra context
     */
    public static function logSessionEvent(int $sessionId, string $event, array $data = []): void
    {
        try {
            DB::table('nexus_audit_log')->insert([
                'event'            => $event,
                'actor_type'       => 'engine',
                'actor_id'         => null,
                'subject_user_id'  => null,
                'nexus_session_id' => $sessionId,
                'sell_id'          => null,
                'bid_id'           => null,
                'context'          => self::encodeContext($data),
                'notes'            => ucfirst(str_replace('_', ' ', $event)),
                'created_at'       => now(),
            ]);
        } catch (\Throwable $e) {
            Log::error('[NexusAuditLogger] Failed to write session event to DB', [
                'error'      => $e->getMessage(),
                'session_id' => $sessionId,
                'event'      => $event,
            ]);
        }
    }

    /**
     * Encode context data as JSON. Returns null if empty.
     * Recursively rounds floats to 4 decimal places to prevent
     * PHP floating point noise (e.g. 45.0799999999999982...).
     */
    private static function encodeContext(array $data): ?string
    {
        if (empty($data)) return null;
        return json_encode(self::roundFloats($data));
    }

    /**
     * Recursively round all float values in an array to 4 decimal places.
     */
    private static function roundFloats(array $data): array
    {
        foreach ($data as $key => $value) {
            if (is_float($value)) {
                $data[$key] = round($value, 4);
            } elseif (is_array($value)) {
                $data[$key] = self::roundFloats($value);
            }
        }
        return $data;
    }
}
