<?php

namespace App\Services;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;

class LoggingService
{
    protected string $requestId;
    protected array $logs = [];
    protected float $startTime;
    protected ?string $endpoint = null;
    protected ?string $method = null;
    protected ?int $userId = null;
    protected ?string $userEmail = null;
    protected ?int $branchId = null;
    protected ?string $branchName = null;
    protected ?string $ipAddress = null;
    protected mixed $requestPayload = null;

    public function __construct()
    {
        $this->requestId = 'req_' . Str::random(12);
        $this->startTime = microtime(true);
    }

    public function getRequestId(): string
    {
        return $this->requestId;
    }

    public function setRequestContext(
        string $method,
        string $endpoint,
        ?int $userId = null,
        ?string $userEmail = null,
        ?int $branchId = null,
        ?string $branchName = null,
        ?string $ipAddress = null,
        mixed $payload = null
    ): void {
        $this->method = $method;
        $this->endpoint = $endpoint;
        $this->userId = $userId;
        $this->userEmail = $userEmail;
        $this->branchId = $branchId;
        $this->branchName = $branchName;
        $this->ipAddress = $ipAddress;
        $this->requestPayload = $this->sanitizePayload($payload);

        $this->logRequestStart();
    }

    protected function sanitizePayload(mixed $payload): mixed
    {
        if (!is_array($payload)) {
            return $payload;
        }

        $sensitiveKeys = ['password', 'password_confirmation', 'token', 'secret', 'api_key'];

        foreach ($sensitiveKeys as $key) {
            if (isset($payload[$key])) {
                $payload[$key] = '********';
            }
        }

        return $payload;
    }

    protected function logRequestStart(): void
    {
        $this->addLog('REQUEST START', [
            'Request ID' => $this->requestId,
            'Endpoint' => "{$this->method} {$this->endpoint}",
            'User' => $this->userId ? "{$this->userEmail} (ID: {$this->userId})" : 'Guest',
            'Branch' => $this->branchId ? "{$this->branchName} (ID: {$this->branchId})" : 'N/A',
            'IP' => $this->ipAddress ?? 'Unknown',
            'Payload' => $this->requestPayload,
        ]);
    }

    public function addLog(string $label, array|string $data = []): void
    {
        $this->logs[] = [
            'timestamp' => now()->format('Y-m-d H:i:s.u'),
            'label' => $label,
            'data' => $data,
        ];
    }

    public function logService(string $serviceMethod, mixed $input = null, mixed $output = null): void
    {
        $this->addLog("SERVICE: {$serviceMethod}", [
            'Input' => $this->sanitizePayload($input),
            'Output' => $output,
        ]);
    }

    public function logRepository(string $repositoryMethod, ?float $executionTimeMs = null): void
    {
        $data = [];
        if ($executionTimeMs !== null) {
            $data['Query executed in'] = round($executionTimeMs, 2) . 'ms';
        }
        $this->addLog("REPOSITORY: {$repositoryMethod}", $data);
    }

    public function logError(string $message, array $context = []): void
    {
        $this->addLog("ERROR: {$message}", $context);
    }

    public function logInfo(string $message, array $context = []): void
    {
        $this->addLog("INFO: {$message}", $context);
    }

    public function flush(int $statusCode, mixed $response = null): void
    {
        $responseTime = round((microtime(true) - $this->startTime) * 1000, 2);

        $this->addLog('REQUEST END', [
            'Status' => $statusCode,
            'Response Time' => $responseTime . 'ms',
            'Response' => $this->truncateResponse($response),
        ]);

        $this->writeToFile();
    }

    protected function truncateResponse(mixed $response): mixed
    {
        if (is_string($response) && strlen($response) > 1000) {
            return substr($response, 0, 1000) . '... [truncated]';
        }

        if (is_array($response)) {
            $encoded = json_encode($response);
            if (strlen($encoded) > 2000) {
                return ['_truncated' => true, '_message' => 'Response too large to log'];
            }
        }

        return $response;
    }

    protected function writeToFile(): void
    {
        $date = now()->format('Y-m-d');
        $directory = storage_path("logs/api/{$date}");

        if (!File::isDirectory($directory)) {
            File::makeDirectory($directory, 0755, true);
        }

        $filePath = "{$directory}/{$this->requestId}.log";
        $content = $this->formatLogs();

        File::put($filePath, $content);
    }

    protected function formatLogs(): string
    {
        $output = [];
        $output[] = str_repeat('=', 80);
        $output[] = "REQUEST LOG: {$this->requestId}";
        $output[] = str_repeat('=', 80);
        $output[] = '';

        foreach ($this->logs as $log) {
            $output[] = "[{$log['timestamp']}] {$log['label']}";

            if (is_array($log['data']) && !empty($log['data'])) {
                foreach ($log['data'] as $key => $value) {
                    $formattedValue = $this->formatValue($value);
                    $output[] = "    {$key}: {$formattedValue}";
                }
            } elseif (is_string($log['data']) && !empty($log['data'])) {
                $output[] = "    {$log['data']}";
            }

            $output[] = '';
        }

        $output[] = str_repeat('=', 80);

        return implode(PHP_EOL, $output);
    }

    protected function formatValue(mixed $value): string
    {
        if (is_null($value)) {
            return 'null';
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if (is_array($value) || is_object($value)) {
            return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        }

        return (string) $value;
    }

    public function getLogs(): array
    {
        return $this->logs;
    }
}
