package oci

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"time"

	"golang.org/x/net/proxy"

	"github.com/oracle/oci-go-sdk/v65/common"
)

// ProxySpec 是租户关联的出站代理;密码已解密,仅在进程内传递。
type ProxySpec struct {
	Type     string // "socks5" / "http" / "https"
	Host     string
	Port     int
	Username string
	Password string
}

// proxyClientTimeout 与 SDK 默认 HTTPClient 超时保持一致。
const proxyClientTimeout = 60 * time.Second

// 阶段超时对齐 SDK 直连 Transport 模板(transport_template_provider):连不上的
// 代理快速失败,而不是拖满总超时;responses 直通去掉总超时后这是建立阶段的兜底之一。
const (
	proxyDialTimeout         = 30 * time.Second
	proxyTLSHandshakeTimeout = 10 * time.Second
)

// applyProxy 在 SDK client 构造后统一挂出站代理;未关联代理时不动默认配置。
// 所有 New*ClientWithConfigurationProvider 调用点构造成功后都必须经过这里。
func applyProxy(base *common.BaseClient, cred Credentials) {
	if hc := proxyHTTPClient(cred.Proxy); hc != nil {
		base.HTTPClient = hc
	}
}

// proxyHTTPClient 按代理配置构造 http.Client;nil 或非法配置返回 nil(走直连)。
func proxyHTTPClient(p *ProxySpec) *http.Client {
	if p == nil || p.Host == "" || p.Port <= 0 {
		return nil
	}
	tr := transportFor(p)
	if tr == nil {
		return nil
	}
	return &http.Client{Transport: tr, Timeout: proxyClientTimeout}
}

// HTTPClientFor 供面板自身请求(如代理出口地理探测)复用与租户 SDK
// 完全一致的代理链路;nil 或非法配置返回 nil。
func HTTPClientFor(p *ProxySpec) *http.Client {
	return proxyHTTPClient(p)
}

// transportFor 构造代理 Transport:http / https 走 CONNECT,socks5 走拨号器。
func transportFor(p *ProxySpec) *http.Transport {
	addr := fmt.Sprintf("%s:%d", p.Host, p.Port)
	dialer := &net.Dialer{Timeout: proxyDialTimeout}
	if p.Type == "http" || p.Type == "https" {
		u := &url.URL{Scheme: p.Type, Host: addr}
		if p.Username != "" {
			u.User = url.UserPassword(p.Username, p.Password)
		}
		return &http.Transport{
			Proxy:               http.ProxyURL(u),
			DialContext:         dialer.DialContext,
			TLSHandshakeTimeout: proxyTLSHandshakeTimeout,
		}
	}
	var auth *proxy.Auth
	if p.Username != "" {
		auth = &proxy.Auth{User: p.Username, Password: p.Password}
	}
	d, err := proxy.SOCKS5("tcp", addr, auth, dialer)
	if err != nil {
		return nil
	}
	cd, ok := d.(proxy.ContextDialer)
	if !ok {
		return nil
	}
	return &http.Transport{
		DialContext:         cd.DialContext,
		TLSHandshakeTimeout: proxyTLSHandshakeTimeout,
	}
}
