<?php

namespace App\Services;

use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class WeatherService
{
    private const BASE_SHORT = 'https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst';

    private const BASE_MID_TA = 'https://apis.data.go.kr/1360000/MidFcstInfoService/getMidTa';

    private const BASE_MID_LAND = 'https://apis.data.go.kr/1360000/MidFcstInfoService/getMidLandFcst';

    // 서울 격자 좌표
    private const NX = 60;

    private const NY = 127;

    // 중기예보 지역 코드 (서울)
    private const MID_TA_REG_ID = '11B10101';   // 서울 기온

    private const MID_LAND_REG_ID = '11B00000';  // 서울·인천·경기

    private string $serviceKey;

    public function __construct()
    {
        $this->serviceKey = config('services.kma.service_key', '');
    }

    /**
     * 7일 예보 데이터 반환 (완전한 데이터 3시간, 불완전 10분 캐시)
     */
    public function getWeeklyForecast(): array
    {
        if (empty($this->serviceKey)) {
            return [];
        }

        $cached = Cache::get('weather_forecast_7days');
        if ($cached !== null) {
            return $cached;
        }

        try {
            $forecasts = $this->buildForecast();
        } catch (\Throwable $e) {
            Log::warning('WeatherService: 예보 조합 실패', ['error' => $e->getMessage()]);

            return [];
        }

        // 데이터 없는 날이 있으면 10분만 캐시 (API 재시도 유도)
        $hasIncomplete = collect($forecasts)->contains(
            fn ($fc) => $fc['tmn'] === null && $fc['tmx'] === null && $fc['icon'] === null
        );
        Cache::put('weather_forecast_7days', $forecasts, $hasIncomplete ? 600 : 10800);

        return $forecasts;
    }

    private function buildForecast(): array
    {
        $today = Carbon::today();

        // 1) 단기예보 (오늘 ~ +2일, 최대 3일)
        $short = $this->fetchShortForecast();

        // 2) 중기기온 + 중기날씨 (4일 ~ 10일)
        $midTa = $this->fetchMidTemperature();
        $midLand = $this->fetchMidLandForecast();

        // 오늘부터 7일간 데이터 조합 (단기 + 중기 병합)
        $forecasts = [];
        for ($i = 0; $i < 7; $i++) {
            $date = $today->copy()->addDays($i)->format('Ymd');

            $shortData = $short[$date] ?? null;

            // 중기예보 데이터
            $midTmn = $midTa["taMin{$i}"] ?? null;
            $midTmx = $midTa["taMax{$i}"] ?? null;
            $midWf = $midLand["wf{$i}Am"] ?? ($midLand["wf{$i}"] ?? '');
            $midPop = max($midLand["rnSt{$i}Am"] ?? 0, $midLand["rnSt{$i}Pm"] ?? 0, $midLand["rnSt{$i}"] ?? 0);
            $midIcon = ! empty($midWf) ? $this->midWeatherToIcon($midWf) : null;

            // 단기예보에 TMN/TMX가 있으면 완전한 데이터로 판단
            $shortComplete = $shortData && ($shortData['tmn'] !== null || $shortData['tmx'] !== null);

            if ($shortComplete) {
                // 단기예보 우선, 기온만 없으면 중기로 보충
                $forecasts[] = [
                    'date' => $date,
                    'tmn' => $shortData['tmn'] ?? $midTmn,
                    'tmx' => $shortData['tmx'] ?? $midTmx,
                    'icon' => $shortData['icon'] ?? $midIcon,
                    'weather_text' => $shortData['weather_text'] ?: $midWf,
                    'pop' => $shortData['pop'] ?? $midPop,
                ];
            } else {
                // 중기예보 우선 (단기예보가 없거나 TMN/TMX 미포함)
                $forecasts[] = [
                    'date' => $date,
                    'tmn' => $midTmn ?? ($shortData['tmn'] ?? null),
                    'tmx' => $midTmx ?? ($shortData['tmx'] ?? null),
                    'icon' => $midIcon ?? ($shortData['icon'] ?? null),
                    'weather_text' => ! empty($midWf) ? $midWf : ($shortData['weather_text'] ?? ''),
                    'pop' => $midPop > 0 ? $midPop : ($shortData['pop'] ?? 0),
                ];
            }
        }

        return $forecasts;
    }

    /**
     * 기상청 API 호출 헬퍼 — serviceKey 이중 인코딩 방지
     */
    private function callApi(string $baseUrl, array $params): ?array
    {
        $params['serviceKey'] = $this->serviceKey;
        $params['dataType'] = 'JSON';
        $queryString = http_build_query($params, '', '&', PHP_QUERY_RFC3986);

        $response = Http::timeout(10)
            ->withOptions(['query' => $queryString])
            ->get($baseUrl);

        if (! $response->successful()) {
            Log::warning('WeatherService API 실패', [
                'url' => $baseUrl,
                'status' => $response->status(),
            ]);

            return null;
        }

        return $response->json();
    }

    /**
     * 단기예보 API 호출 — TMN/TMX/SKY/PTY 추출
     */
    private function fetchShortForecast(): array
    {
        $now = Carbon::now();
        $baseDate = $now->hour < 2 ? $now->copy()->subDay()->format('Ymd') : $now->format('Ymd');

        $body = $this->callApi(self::BASE_SHORT, [
            'pageNo' => 1,
            'numOfRows' => 1000,
            'base_date' => $baseDate,
            'base_time' => '0200',
            'nx' => self::NX,
            'ny' => self::NY,
        ]);

        if (! $body) {
            return [];
        }

        $items = data_get($body, 'response.body.items.item', []);
        if (empty($items)) {
            return [];
        }

        // 날짜별 그룹
        $grouped = [];
        foreach ($items as $item) {
            $fcstDate = $item['fcstDate'];
            $category = $item['category'];
            $value = $item['fcstValue'];

            if (! isset($grouped[$fcstDate])) {
                $grouped[$fcstDate] = ['date' => $fcstDate, 'tmn' => null, 'tmx' => null, 'sky' => null, 'pty' => null, 'pop' => 0];
            }

            match ($category) {
                'TMN' => $grouped[$fcstDate]['tmn'] = (int) $value,
                'TMX' => $grouped[$fcstDate]['tmx'] = (int) $value,
                'SKY' => $grouped[$fcstDate]['sky'] ??= (int) $value,
                'PTY' => $grouped[$fcstDate]['pty'] ??= (int) $value,
                'POP' => $grouped[$fcstDate]['pop'] = max($grouped[$fcstDate]['pop'], (int) $value),
                default => null,
            };
        }

        $result = [];
        foreach ($grouped as $date => $data) {
            $icon = $this->shortWeatherToIcon($data['sky'] ?? 1, $data['pty'] ?? 0);
            $result[$date] = [
                'date' => $date,
                'tmn' => $data['tmn'],
                'tmx' => $data['tmx'],
                'icon' => $icon,
                'weather_text' => $this->iconToText($icon),
                'pop' => $data['pop'],
            ];
        }

        return $result;
    }

    /**
     * 중기기온 API 호출 (최신 발표 우선, 이전 발표로 빈 필드 보충)
     *
     * 기상청 중기예보 API 특성:
     * - 06시 발표: D+4 ~ D+10 제공 (D+3 없음)
     * - 18시 발표: D+5 ~ D+10 제공 (D+3, D+4 없음)
     * → 18시에는 최신 데이터가 D+5부터만 있으므로, 06시 데이터로 D+4를 보충
     */
    private function fetchMidTemperature(): array
    {
        return $this->fetchMidMerged(self::BASE_MID_TA, self::MID_TA_REG_ID);
    }

    /**
     * 중기날씨 API 호출 (최신 발표 우선, 이전 발표로 빈 필드 보충)
     */
    private function fetchMidLandForecast(): array
    {
        return $this->fetchMidMerged(self::BASE_MID_LAND, self::MID_LAND_REG_ID);
    }

    /**
     * 중기예보 데이터를 여러 발표시각에서 병합 (최신 우선)
     */
    private function fetchMidMerged(string $baseUrl, string $regId): array
    {
        $merged = [];

        foreach ($this->getMidBaseTmFcCandidates() as $tmFc) {
            $body = $this->callApi($baseUrl, [
                'pageNo' => 1,
                'numOfRows' => 10,
                'regId' => $regId,
                'tmFc' => $tmFc,
            ]);

            $items = $body ? data_get($body, 'response.body.items.item', []) : [];
            if (empty($items)) {
                continue;
            }

            // 최신 발표 데이터 우선, 이전 발표로 빈 필드만 보충
            foreach ($items[0] as $key => $value) {
                if ($value !== null && $value !== '' && ! isset($merged[$key])) {
                    $merged[$key] = $value;
                }
            }
        }

        return $merged;
    }

    /**
     * 중기예보 발표시각 후보 목록 (우선순위순, fallback 포함)
     */
    private function getMidBaseTmFcCandidates(): array
    {
        $now = Carbon::now();
        $today = $now->format('Ymd');
        $yesterday = $now->copy()->subDay()->format('Ymd');

        if ($now->hour >= 18) {
            // 18시 발표 → 06시 발표 → 전일 18시 발표
            return [$today.'1800', $today.'0600', $yesterday.'1800'];
        } elseif ($now->hour >= 6) {
            // 06시 발표 → 전일 18시 발표
            return [$today.'0600', $yesterday.'1800'];
        }

        // 자정~06시: 전일 18시 발표 → 전일 06시 발표
        return [$yesterday.'1800', $yesterday.'0600'];
    }

    /**
     * 단기예보 SKY + PTY → 아이콘 이름
     */
    private function shortWeatherToIcon(int $sky, int $pty): string
    {
        if ($pty > 0) {
            return match ($pty) {
                1, 4 => 'rain',
                2 => 'sleet',
                3 => 'snow',
                5 => 'rain',
                6 => 'sleet',
                7 => 'snow',
                default => 'cloud',
            };
        }

        return match ($sky) {
            1 => 'sun',
            3 => 'cloud-sun',
            4 => 'cloud',
            default => 'sun',
        };
    }

    /**
     * 중기예보 한글 날씨 문자열 → 아이콘 이름
     */
    private function midWeatherToIcon(string $wf): string
    {
        if (empty($wf)) {
            return 'cloud';
        }

        if (str_contains($wf, '눈') && str_contains($wf, '비')) {
            return 'sleet';
        }
        if (str_contains($wf, '눈')) {
            return 'snow';
        }
        if (str_contains($wf, '비') || str_contains($wf, '소나기')) {
            return 'rain';
        }
        if (str_contains($wf, '흐림')) {
            return 'cloud';
        }
        if (str_contains($wf, '구름많음') || str_contains($wf, '구름')) {
            return 'cloud-sun';
        }
        if (str_contains($wf, '맑음')) {
            return 'sun';
        }

        return 'cloud';
    }

    /**
     * 아이콘 → 한글 텍스트
     */
    private function iconToText(string $icon): string
    {
        return match ($icon) {
            'sun' => '맑음',
            'cloud-sun' => '구름많음',
            'cloud' => '흐림',
            'rain' => '비',
            'snow' => '눈',
            'sleet' => '비/눈',
            default => '',
        };
    }
}
