<?php

namespace App\Http\Middleware;

use App\Models\ApiRequestLog;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

/**
 * API 요청/응답 로깅 미들웨어
 *
 * DB와 로그 파일에 이중으로 저장
 */
class LogApiRequest
{
    /**
     * 로깅에서 제외할 경로 패턴
     */
    protected array $exceptPaths = [
        'api/health',
        'api-docs*',
        'docs*',
    ];

    /**
     * 민감한 헤더 (마스킹 처리)
     */
    protected array $sensitiveHeaders = [
        'authorization',
        'x-api-key',
        'cookie',
    ];

    /**
     * 민감한 필드 (마스킹 처리)
     */
    protected array $sensitiveFields = [
        'password',
        'password_confirmation',
        'current_password',
        'new_password',
        'token',
        'secret',
        'api_key',
    ];

    public function handle(Request $request, Closure $next): Response
    {
        // 제외 경로 체크
        if ($this->shouldSkip($request)) {
            return $next($request);
        }

        $startTime = microtime(true);

        $response = $next($request);

        $this->logRequest($request, $response, $startTime);

        return $response;
    }

    protected function shouldSkip(Request $request): bool
    {
        foreach ($this->exceptPaths as $pattern) {
            if ($request->is($pattern)) {
                return true;
            }
        }

        return false;
    }

    protected function logRequest(Request $request, Response $response, float $startTime): void
    {
        try {
            $durationMs = (int) ((microtime(true) - $startTime) * 1000);

            // 사용자/테넌트 정보 가져오기 (ApiKeyMiddleware에서 설정된 값 우선)
            // api_user는 이미 user_id 값, tenant_id도 마찬가지
            $userId = app()->bound('api_user') ? app('api_user') : $request->user()?->id;
            $tenantId = app()->bound('tenant_id') ? app('tenant_id') : $request->user()?->current_tenant_id;

            // 그룹 ID 생성 (동일 tenant+user의 연속 요청 묶기)
            $groupId = $this->getOrCreateGroupId($tenantId, $userId);

            $logData = [
                'method' => $request->method(),
                'url' => $request->fullUrl(),
                'route_name' => $request->route()?->getName(),
                'request_headers' => $this->maskSensitiveHeaders($request->headers->all()),
                'request_body' => $this->maskSensitiveFields($request->all()),
                'request_query' => $request->query(),
                'response_status' => $response->getStatusCode(),
                'response_body' => $this->truncateResponse($response->getContent()),
                'duration_ms' => $durationMs,
                'ip_address' => $request->ip(),
                'user_agent' => $request->userAgent(),
                'user_id' => $userId,
                'tenant_id' => $tenantId,
                'group_id' => $groupId,
            ];

            // DB 저장
            ApiRequestLog::create($logData);

            // 로그 파일 저장
            $this->writeToLogFile($logData);

        } catch (\Throwable $e) {
            // 로깅 실패가 비즈니스 로직에 영향을 주지 않도록 조용히 처리
            Log::error('API 로깅 실패', [
                'error' => $e->getMessage(),
                'url' => $request->fullUrl(),
            ]);
        }
    }

    protected function maskSensitiveHeaders(array $headers): array
    {
        foreach ($this->sensitiveHeaders as $header) {
            if (isset($headers[$header])) {
                $headers[$header] = ['***MASKED***'];
            }
        }

        return $headers;
    }

    protected function maskSensitiveFields(array $data): array
    {
        foreach ($data as $key => $value) {
            if (in_array(strtolower($key), $this->sensitiveFields)) {
                $data[$key] = '***MASKED***';
            } elseif (is_array($value)) {
                $data[$key] = $this->maskSensitiveFields($value);
            }
        }

        return $data;
    }

    protected function truncateResponse(?string $content): ?string
    {
        if ($content === null) {
            return null;
        }

        // 10KB 이상이면 잘라내기
        $maxLength = 10 * 1024;
        if (strlen($content) > $maxLength) {
            return substr($content, 0, $maxLength).'...[TRUNCATED]';
        }

        return $content;
    }

    protected function writeToLogFile(array $logData): void
    {
        $logMessage = sprintf(
            '[%s] %s %s | Status: %d | Duration: %dms | IP: %s | User: %s',
            now()->format('Y-m-d H:i:s'),
            $logData['method'],
            $logData['url'],
            $logData['response_status'],
            $logData['duration_ms'],
            $logData['ip_address'] ?? 'N/A',
            $logData['user_id'] ?? 'guest'
        );

        Log::channel('api')->info($logMessage, [
            'request_body' => $logData['request_body'],
            'response_status' => $logData['response_status'],
        ]);
    }

    /**
     * 그룹 ID 생성 또는 기존 그룹 ID 반환
     * 동일 tenant+user의 5초 내 요청은 같은 그룹으로 묶음
     */
    protected function getOrCreateGroupId(?int $tenantId, ?int $userId): string
    {
        $cacheKey = "api_log_group:{$tenantId}:{$userId}";
        $groupTtl = 5; // 5초 내 요청은 같은 그룹

        // 캐시에서 기존 그룹 ID 확인
        $existingGroupId = cache()->get($cacheKey);

        if ($existingGroupId) {
            // TTL 갱신 (연속 요청 시 그룹 유지)
            cache()->put($cacheKey, $existingGroupId, $groupTtl);

            return $existingGroupId;
        }

        // 새 그룹 ID 생성
        $newGroupId = now()->format('Ymd_His_').substr(md5(uniqid((string) mt_rand(), true)), 0, 8);
        cache()->put($cacheKey, $newGroupId, $groupTtl);

        return $newGroupId;
    }
}
