package handlers

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"sync"
	"time"

	"cmr-backend/internal/apperr"
	"cmr-backend/internal/httpx"
)

type RegionOptionsHandler struct {
	client *http.Client
	mu     sync.Mutex
	cache  []regionProvince
}

type regionProvince struct {
	Code   string       `json:"code"`
	Name   string       `json:"name"`
	Cities []regionCity `json:"cities"`
}

type regionCity struct {
	Code string `json:"code"`
	Name string `json:"name"`
}

type remoteProvince struct {
	Code string `json:"code"`
	Name string `json:"name"`
}

type remoteCity struct {
	Code     string `json:"code"`
	Name     string `json:"name"`
	Province string `json:"province"`
}

func NewRegionOptionsHandler() *RegionOptionsHandler {
	return &RegionOptionsHandler{
		client: &http.Client{Timeout: 12 * time.Second},
	}
}

func (h *RegionOptionsHandler) Get(w http.ResponseWriter, r *http.Request) {
	items, err := h.load(r.Context())
	if err != nil {
		httpx.WriteError(w, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": items})
}

func (h *RegionOptionsHandler) load(ctx context.Context) ([]regionProvince, error) {
	h.mu.Lock()
	if len(h.cache) > 0 {
		cached := h.cache
		h.mu.Unlock()
		return cached, nil
	}
	h.mu.Unlock()

	// Data source:
	// https://github.com/uiwjs/province-city-china
	// Using province + city JSON only, then reducing to the province/city structure
	// needed by ops workbench location management.
	provinces, err := h.fetchProvinces(ctx, "https://unpkg.com/province-city-china/dist/province.json")
	if err != nil {
		return nil, err
	}
	cities, err := h.fetchCities(ctx, "https://unpkg.com/province-city-china/dist/city.json")
	if err != nil {
		return nil, err
	}

	cityMap := make(map[string][]regionCity)
	for _, item := range cities {
		if item.Province == "" || item.Code == "" {
			continue
		}
		fullCode := item.Province + item.Code + "00"
		cityMap[item.Province] = append(cityMap[item.Province], regionCity{
			Code: fullCode,
			Name: item.Name,
		})
	}
	for key := range cityMap {
		sort.Slice(cityMap[key], func(i, j int) bool { return cityMap[key][i].Code < cityMap[key][j].Code })
	}

	items := make([]regionProvince, 0, len(provinces))
	for _, item := range provinces {
		if len(item.Code) < 2 {
			continue
		}
		provinceCode := item.Code[:2]
		province := regionProvince{
			Code: item.Code,
			Name: item.Name,
		}
		if entries := cityMap[provinceCode]; len(entries) > 0 {
			province.Cities = entries
		} else {
			// 直辖市 / 特殊地区没有单独的地级市列表时，退化成自身即可。
			province.Cities = []regionCity{{
				Code: item.Code,
				Name: item.Name,
			}}
		}
		items = append(items, province)
	}

	h.mu.Lock()
	h.cache = items
	h.mu.Unlock()
	return items, nil
}

func (h *RegionOptionsHandler) fetchProvinces(ctx context.Context, url string) ([]remoteProvince, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
	}
	resp, err := h.client.Do(req)
	if err != nil {
		return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", fmt.Sprintf("省级数据拉取失败: %d", resp.StatusCode))
	}
	var items []remoteProvince
	if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
		return nil, apperr.New(http.StatusBadGateway, "region_source_invalid", "省级数据格式无效")
	}
	return items, nil
}

func (h *RegionOptionsHandler) fetchCities(ctx context.Context, url string) ([]remoteCity, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
	}
	resp, err := h.client.Do(req)
	if err != nil {
		return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", fmt.Sprintf("市级数据拉取失败: %d", resp.StatusCode))
	}
	var items []remoteCity
	if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
		return nil, apperr.New(http.StatusBadGateway, "region_source_invalid", "市级数据格式无效")
	}
	return items, nil
}
