package service

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"oci-portal/internal/model"
)

// geoProbeTimeout 覆盖单次探测(含 fallback)的总时长。
const geoProbeTimeout = 20 * time.Second

// geoResult 是代理出口地理探测结果;空值表示探测失败(前端显示「未知」)。
type geoResult struct {
	Country string
	City    string
}

// geoEndpoint 是单个出口地理 API 的地址与解析器。
type geoEndpoint struct {
	url   string
	parse func([]byte) (geoResult, bool)
}

// geoEndpoints 依次尝试的免费出口地理 API,第一个成功即返回。
// 主选 ip-api.com:免费 45 req/min(按出口 IP 计)、直接返回中文,但仅提供 http;
// 备选 ipwho.is:https、无显式限频,仅英文。均不带 IP 参数,即查代理出口本身。
var geoEndpoints = []geoEndpoint{
	{url: "http://ip-api.com/json/?fields=status,country,city&lang=zh-CN", parse: parseIPAPI},
	{url: "https://ipwho.is/?fields=success,country,city", parse: parseIPWhois},
}

// parseIPAPI 解析 ip-api.com 响应。
func parseIPAPI(b []byte) (geoResult, bool) {
	var r struct {
		Status  string `json:"status"`
		Country string `json:"country"`
		City    string `json:"city"`
	}
	if json.Unmarshal(b, &r) != nil || r.Status != "success" || r.Country == "" {
		return geoResult{}, false
	}
	return geoResult{Country: r.Country, City: r.City}, true
}

// parseIPWhois 解析 ipwho.is 响应。
func parseIPWhois(b []byte) (geoResult, bool) {
	var r struct {
		Success bool   `json:"success"`
		Country string `json:"country"`
		City    string `json:"city"`
	}
	if json.Unmarshal(b, &r) != nil || !r.Success || r.Country == "" {
		return geoResult{}, false
	}
	return geoResult{Country: r.Country, City: r.City}, true
}

// probeGeoOnce 经指定 client 请求单个 geo API;任何失败返回 false。
func probeGeoOnce(ctx context.Context, hc *http.Client, url string, parse func([]byte) (geoResult, bool)) (geoResult, bool) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return geoResult{}, false
	}
	resp, err := hc.Do(req)
	if err != nil {
		return geoResult{}, false
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
	if err != nil || resp.StatusCode != http.StatusOK {
		return geoResult{}, false
	}
	return parse(body)
}

// probeGeoAsync 后台探测出口地理并回填;创建 / 更新后调用,Wait 收敛。
func (s *ProxyService) probeGeoAsync(id uint) {
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		ctx, cancel := context.WithTimeout(context.Background(), geoProbeTimeout)
		defer cancel()
		s.probeGeo(ctx, id)
	}()
}

// probeGeo 经该代理链路依次尝试 geo API 并写回地区字段;
// 请求全部失败也写空值+探测时间(前端显示「未知」),避免主机变更后残留旧地区;
// client 构造失败(spec 非法)则不动数据。
func (s *ProxyService) probeGeo(ctx context.Context, id uint) {
	spec, err := s.SpecOf(ctx, &id)
	if err != nil {
		return
	}
	hc := s.geoClientFor(spec)
	if hc == nil {
		return
	}
	res := geoResult{}
	for _, ep := range geoEndpoints {
		if r, ok := probeGeoOnce(ctx, hc, ep.url, ep.parse); ok {
			res = r
			break
		}
	}
	var row model.Proxy
	if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil {
		return
	}
	now := time.Now()
	row.Country, row.City, row.GeoAt = res.Country, res.City, &now
	s.db.WithContext(ctx).Save(&row)
}

// Probe 同步重测代理出口地理并返回最新视图;供手动「重测」入口调用。
func (s *ProxyService) Probe(ctx context.Context, id uint) (ProxyView, error) {
	var row model.Proxy
	if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil {
		return ProxyView{}, fmt.Errorf("find proxy %d: %w", id, err)
	}
	pctx, cancel := context.WithTimeout(ctx, geoProbeTimeout)
	defer cancel()
	s.probeGeo(pctx, id)
	if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil {
		return ProxyView{}, fmt.Errorf("find proxy %d: %w", id, err)
	}
	return s.viewOf(ctx, &row)
}
