package utils

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

	"github.com/apernet/hysteria/core/client"
)

const (
	updateCheckEndpoint = "https://api.hy2.io/v1/update"
	updateCheckTimeout  = 10 * time.Second
)

type UpdateChecker struct {
	CurrentVersion string
	Platform       string
	Architecture   string
	Channel        string
	Side           string
	Client         *http.Client
}

func NewServerUpdateChecker(currentVersion, platform, architecture, channel string) *UpdateChecker {
	return &UpdateChecker{
		CurrentVersion: currentVersion,
		Platform:       platform,
		Architecture:   architecture,
		Channel:        channel,
		Side:           "server",
		Client: &http.Client{
			Timeout: updateCheckTimeout,
		},
	}
}

// NewClientUpdateChecker ensures that update checks are routed through a HyClient,
// not being sent directly. This safeguard is CRITICAL, especially in scenarios where
// users use Hysteria to bypass censorship. Making direct HTTPS requests to the API
// endpoint could be easily spotted by censors (through SNI, for example), and could
// serve as a signal to identify and penalize Hysteria users.
func NewClientUpdateChecker(currentVersion, platform, architecture, channel string, hyClient client.Client) *UpdateChecker {
	return &UpdateChecker{
		CurrentVersion: currentVersion,
		Platform:       platform,
		Architecture:   architecture,
		Channel:        channel,
		Side:           "client",
		Client: &http.Client{
			Timeout: updateCheckTimeout,
			Transport: &http.Transport{
				DialContext: func(_ context.Context, network, addr string) (net.Conn, error) {
					// Unfortunately HyClient doesn't support context for now
					return hyClient.TCP(addr)
				},
			},
		},
	}
}

type UpdateResponse struct {
	HasUpdate     bool   `json:"update"`
	LatestVersion string `json:"lver"`
	URL           string `json:"url"`
	Urgent        bool   `json:"urgent"`
}

func (uc *UpdateChecker) Check() (*UpdateResponse, error) {
	url := fmt.Sprintf("%s?cver=%s&plat=%s&arch=%s&chan=%s&side=%s",
		updateCheckEndpoint,
		uc.CurrentVersion,
		uc.Platform,
		uc.Architecture,
		uc.Channel,
		uc.Side,
	)
	resp, err := uc.Client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}
	var uResp UpdateResponse
	decoder := json.NewDecoder(resp.Body)
	if err := decoder.Decode(&uResp); err != nil {
		return nil, err
	}
	return &uResp, nil
}
