<?php

namespace App\Helpers;

use App\Exceptions\DuplicateCodeException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\HttpException;

class ApiResponse
{
    /**
     * ISO 8601 날짜를 Y-m-d 형식으로 변환
     *
     * @param  mixed  $data  변환할 데이터 (배열, 객체, 페이지네이션 등)
     * @return mixed 변환된 데이터
     */
    public static function formatDates(mixed $data): mixed
    {
        // null이면 그대로 반환
        if ($data === null) {
            return null;
        }

        // Paginator 처리
        if ($data instanceof \Illuminate\Pagination\LengthAwarePaginator ||
            $data instanceof \Illuminate\Pagination\Paginator) {
            $items = self::formatDates($data->items());
            $data->setCollection(collect($items));

            return $data;
        }

        // Collection 처리
        if ($data instanceof \Illuminate\Support\Collection) {
            return $data->map(fn ($item) => self::formatDates($item));
        }

        // Model 처리
        if ($data instanceof \Illuminate\Database\Eloquent\Model) {
            return self::formatDates($data->toArray());
        }

        // 배열 처리
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                if (is_string($value) && self::isIso8601Date($value)) {
                    $data[$key] = self::toDateFormat($value);
                } elseif (is_array($value) || is_object($value)) {
                    $data[$key] = self::formatDates($value);
                }
            }

            return $data;
        }

        // 단일 문자열이 ISO 8601 날짜인 경우
        if (is_string($data) && self::isIso8601Date($data)) {
            return self::toDateFormat($data);
        }

        return $data;
    }

    /**
     * ISO 8601 날짜 형식인지 확인
     */
    private static function isIso8601Date(string $value): bool
    {
        // 2025-12-22T15:00:00.000000Z 형식 매칭
        return (bool) preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z?$/', $value);
    }

    /**
     * ISO 8601 날짜를 Y-m-d 형식으로 변환
     */
    private static function toDateFormat(string $value): string
    {
        try {
            return \Carbon\Carbon::parse($value)->format('Y-m-d');
        } catch (\Exception $e) {
            return $value;
        }
    }

    public function normalizeFiles(array $laravelFiles): array
    {
        $files = ['name' => [], 'type' => [], 'tmp_name' => [], 'size' => [], 'fileType' => []];
        foreach ($laravelFiles as $file) {
            $files['name'][] = $file->getClientOriginalName();
            $files['type'][] = $file->getClientMimeType();
            $files['tmp_name'][] = $file->getPathname();
            $files['size'][] = $file->getSize();
            $files['fileType'][] = '';
        }

        return $files;
    }

    // DebugQuery Helper
    public static function debugQueryLog(): array
    {
        $logs = DB::getQueryLog();

        return collect($logs)
            ->skip(3)
            ->map(function ($log) {
                $query = $log['query'];
                foreach ($log['bindings'] as $binding) {
                    $binding = is_numeric($binding) ? $binding : "'".addslashes($binding)."'";
                    $query = preg_replace('/\\?/', $binding, $query, 1);
                }

                // \n 제거
                $query = str_replace(["\n", "\r"], ' ', $query)." (time: {$log['time']})";

                return trim($query);
            })->toArray();
    }

    // ApiResponse Helper
    public static function success(
        $data = null,
        string $message = '요청 성공',
        array $debug = [],
        int $statusCode = 200
    ): JsonResponse {
        // ISO 8601 날짜를 Y-m-d 형식으로 변환
        $formattedData = self::formatDates($data);

        $response = [
            'success' => true,
            'message' => $message,
            'data' => $formattedData,
        ];
        if (! empty($debug)) {
            $response['query'] = $debug;
        }

        return response()->json($response, $statusCode);
    }

    public static function error(
        string $message = '요청 실패',
        int $code = 400,
        array $error = []
    ): JsonResponse {
        return response()->json([
            'success' => false,
            'message' => "[{$code}] {$message}",
            'error' => [
                'code' => $code,
                'details' => $error['details'] ?? null,
            ],
        ], $code);
    }

    public static function validate(
        bool $condition,
        string $message = 'Validation failed',
        int $code = 422,
        array $extra = []
    ): ?JsonResponse {
        return $condition ? null : self::error($message, $code, $extra);
    }

    public static function response($type = '', $query = '', $key = ''): array
    {
        $debug = app()->environment('local') && request()->is('api/*');

        $result = match ($type) {
            'get' => $key ? $query->get()->keyBy($key) : $query->get(),
            'getSub' => $query->get(),
            'count' => $query->count(),
            'first' => $query->first(),
            'success' => 'Success',
            'result' => $query,
            default => null,
        };

        if ($type == 'getSub') {
            $array = $result->map(function ($item) {
                return (array) $item;
            })->toArray();
            foreach ($array as $row) {
                $data[$row[$key]][] = $row;
            }
            $result = $data ?? [];
        }

        $response['data'] = $result;
        $response['query'] = $debug ? self::debugQueryLog() : [];

        // 다음 요청에 로그가 섞이지 않도록 비워준다 (로컬에서만 의미있음)
        if ($debug) {
            DB::flushQueryLog();
        }

        return $response;
    }

    public static function handle(
        callable $callback,
        string $responseTitle = '요청'
    ): JsonResponse {
        try {
            $result = $callback();

            // 이미 JsonResponse면 그대로 반환
            if ($result instanceof JsonResponse) {
                return $result;
            }

            // [신규] 서비스가 에러 ‘신호 배열’을 반환한 경우 감지
            // 허용 포맷 예:
            //   ['error' => 'NO_TENANT', 'code' => 400]
            //   ['code' => 404, 'message' => '데이터 없음']
            if (is_array($result) && (
                array_key_exists('error', $result) ||
                (array_key_exists('code', $result) && is_numeric($result['code']) && (int) $result['code'] >= 400)
            )
            ) {
                $code = (int) ($result['code'] ?? 400);
                $message = (string) ($result['message'] ?? ($result['error'] ?? '서버 에러'));
                $details = $result['details'] ?? null;

                // 에러에도 쿼리 로그 포함되도록 error()가 처리하게 맡김
                return self::error($message, $code, ['details' => $details]);
            }

            // 표준 박스( ['data'=>..., 'query'=>..., 'statusCode'=>...] ) 하위호환
            if (is_array($result) && array_key_exists('data', $result)) {
                $data = $result['data'];
                $debug = $result['query'] ?? [];
                $statusCode = $result['statusCode'] ?? 200;
            } else {
                // 그냥 도메인 결과만 반환한 경우
                $data = $result;
                $debug = (app()->environment('local') && request()->is('api/*'))
                    ? self::debugQueryLog()
                    : [];
                $statusCode = 200;
            }

            return self::success($data, $responseTitle, $debug, $statusCode);

        } catch (\Throwable $e) {
            // 모든 예외를 로깅 (디버깅용)
            Log::error('API Exception', [
                'message' => $e->getMessage(),
                'exception' => get_class($e),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'url' => request()->fullUrl(),
                'method' => request()->method(),
            ]);

            // ValidationException - 422 Unprocessable Entity
            if ($e instanceof \Illuminate\Validation\ValidationException) {
                return response()->json([
                    'success' => false,
                    'message' => __('error.validation_failed'),
                    'errors' => $e->errors(),
                ], 422);
            }

            // 품목 코드 중복 예외 - duplicate_id, duplicate_code 포함
            if ($e instanceof DuplicateCodeException) {
                return response()->json([
                    'success' => false,
                    'message' => $e->getMessage(),
                    'error' => ['code' => 400],
                    'duplicate_id' => $e->getDuplicateId(),
                    'duplicate_code' => $e->getDuplicateCode(),
                ], 400);
            }

            // HttpException 계열은 상태코드/메시지를 그대로 반영
            if ($e instanceof HttpException) {
                $statusCode = $e->getStatusCode();
                // 4xx 클라이언트 에러에는 스택 트레이스 제외, 5xx 서버 에러에만 debug 모드에서 포함
                $includeTrace = $statusCode >= 500 && config('app.debug');

                return self::error(
                    $e->getMessage() ?: '서버 에러',
                    $statusCode,
                    ['details' => $includeTrace ? $e->getTraceAsString() : null]
                );
            }

            // 일반 예외는 500으로 처리, debug 모드에서만 상세 정보 포함
            $errorMessage = config('app.debug') ? $e->getMessage() : '서버 에러';

            return self::error($errorMessage, 500, [
                'details' => config('app.debug') ? $e->getTraceAsString() : null,
                'exception' => config('app.debug') ? get_class($e) : null,
            ]);
        }
    }
}
