package tls

import (
	"crypto"
	"crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"errors"
	"fmt"
	"math/big"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-gost/core/logger"
	"github.com/go-gost/x/config"
	"github.com/patrickmn/go-cache"
)

const (
	VersionTLS10 = "VersionTLS10"
	VersionTLS11 = "VersionTLS11"
	VersionTLS12 = "VersionTLS12"
	VersionTLS13 = "VersionTLS13"
)

var (
	deviceID     string
	deviceIDOnce sync.Once
)

func getDeviceID() string {
	deviceIDOnce.Do(func() {
		idFile := "device.id"
		if data, err := os.ReadFile(idFile); err == nil && len(data) > 0 {
			deviceID = string(data)
			return
		}
		
		deviceID = generateDeviceID()
		
		if err := os.WriteFile(idFile, []byte(deviceID), 0644); err != nil {
			if log := logger.Default(); log != nil {
				log.Warnf("Failed to save device ID: %v", err)
			}
		}
	})
	return deviceID
}

func generateDeviceID() string {
	hostname, _ := os.Hostname()
	if hostname == "" {
		hostname = "unknown"
	}
	
	randomBytes := make([]byte, 16)
	if _, err := rand.Read(randomBytes); err != nil {
		randomBytes = []byte(fmt.Sprintf("%d", time.Now().UnixNano()))
	}
	
	uniqueStr := fmt.Sprintf("%s-%d-%s", hostname, time.Now().UnixNano(), hex.EncodeToString(randomBytes))
	
	hash := sha256.New()
	hash.Write([]byte(uniqueStr))
	return hex.EncodeToString(hash.Sum(nil))[:16]
}

func generateDisguisedDomain(deviceID string) string {
	domains := []string{
		"com", "net", "org", "top", "cc", "info", "biz", "co", "me", "io",
		"us", "cn", "uk", "de", "fr", "jp", "ru", "br", "au", "ca",
	}
	
	prefixes := []string{
		"api", "app", "web", "www", "cdn", "static", "assets", "media", "img", "imgcdn",
		"secure", "ssl", "tls", "https", "gateway", "proxy", "service", "portal", "hub",
		"cloud", "server", "node", "host", "site", "page", "view", "load", "cache",
	}
	
	seed := deviceID[:8]
	
	seedInt := int64(0)
	for _, char := range seed {
		var val int64
		if char >= '0' && char <= '9' {
			val = int64(char - '0')
		} else if char >= 'a' && char <= 'f' {
			val = int64(char - 'a' + 10)
		}
		seedInt = seedInt*16 + val
	}
	
	domainIndex := seedInt % int64(len(domains))
	domain := domains[domainIndex]
	
	prefixIndex := (seedInt / int64(len(domains))) % int64(len(prefixes))
	prefix := prefixes[prefixIndex]
	
	randomSuffix := fmt.Sprintf("%d", seedInt%9999+1000)
	
	return fmt.Sprintf("%s.%s.%s", prefix, randomSuffix, domain)
}

func generateDisguisedOrganization(deviceID string) string {
	companyPrefixes := []string{
		"Cloud", "Digital", "Secure", "Global", "Advanced", "Enterprise", "Professional",
		"Tech", "Data", "Network", "System", "Service", "Solution", "Platform",
		"Smart", "Fast", "Reliable", "Modern", "Innovative", "Dynamic",
	}
	
	companySuffixes := []string{
		"Technologies", "Systems", "Solutions", "Services", "Corporation", "Corp",
		"Inc", "Ltd", "LLC", "Group", "International", "Global", "Enterprises",
		"Software", "Networks", "Security", "Communications", "Infrastructure",
	}
	
	seed := deviceID[:8]
	
	seedInt := int64(0)
	for _, char := range seed {
		var val int64
		if char >= '0' && char <= '9' {
			val = int64(char - '0')
		} else if char >= 'a' && char <= 'f' {
			val = int64(char - 'a' + 10)
		}
		seedInt = seedInt*16 + val
	}
	
	prefixIndex := seedInt % int64(len(companyPrefixes))
	suffixIndex := (seedInt / int64(len(companyPrefixes))) % int64(len(companySuffixes))
	
	prefix := companyPrefixes[prefixIndex]
	suffix := companySuffixes[suffixIndex]
	
	randomSuffix := fmt.Sprintf("%d", seedInt%999+100)
	
	return fmt.Sprintf("%s %s %s", prefix, randomSuffix, suffix)
}

const (
	TLS_RSA_WITH_RC4_128_SHA                      = "TLS_RSA_WITH_RC4_128_SHA"
	TLS_RSA_WITH_3DES_EDE_CBC_SHA                 = "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
	TLS_RSA_WITH_AES_128_CBC_SHA                  = "TLS_RSA_WITH_AES_128_CBC_SHA"
	TLS_RSA_WITH_AES_256_CBC_SHA                  = "TLS_RSA_WITH_AES_256_CBC_SHA"
	TLS_RSA_WITH_AES_128_CBC_SHA256               = "TLS_RSA_WITH_AES_128_CBC_SHA256"
	TLS_RSA_WITH_AES_128_GCM_SHA256               = "TLS_RSA_WITH_AES_128_GCM_SHA256"
	TLS_RSA_WITH_AES_256_GCM_SHA384               = "TLS_RSA_WITH_AES_256_GCM_SHA384"
	TLS_ECDHE_ECDSA_WITH_RC4_128_SHA              = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"
	TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA          = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
	TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA          = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
	TLS_ECDHE_RSA_WITH_RC4_128_SHA                = "TLS_ECDHE_RSA_WITH_RC4_128_SHA"
	TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA           = "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA            = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA            = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
	TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256       = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256         = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256         = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256       = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
	TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384         = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
	TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384       = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256   = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
	TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"

	// TLS 1.3 cipher suites.
	TLS_AES_128_GCM_SHA256       = "TLS_AES_128_GCM_SHA256"
	TLS_AES_256_GCM_SHA384       = "TLS_AES_256_GCM_SHA384"
	TLS_CHACHA20_POLY1305_SHA256 = "TLS_CHACHA20_POLY1305_SHA256"

	// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
	// that the client is doing version fallback. See RFC 7507.
	TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"
)

var (
	cipherSuites = map[uint16]string{
		tls.TLS_RSA_WITH_RC4_128_SHA:                      TLS_RSA_WITH_RC4_128_SHA,
		tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:                 TLS_RSA_WITH_3DES_EDE_CBC_SHA,
		tls.TLS_RSA_WITH_AES_128_CBC_SHA:                  TLS_RSA_WITH_AES_128_CBC_SHA,
		tls.TLS_RSA_WITH_AES_256_CBC_SHA:                  TLS_RSA_WITH_AES_256_CBC_SHA,
		tls.TLS_RSA_WITH_AES_128_CBC_SHA256:               TLS_RSA_WITH_AES_128_CBC_SHA256,
		tls.TLS_RSA_WITH_AES_128_GCM_SHA256:               TLS_RSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_RSA_WITH_AES_256_GCM_SHA384:               TLS_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:              TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:          TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:          TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:                TLS_ECDHE_RSA_WITH_RC4_128_SHA,
		tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:           TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
		tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:            TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
		tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:            TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
		tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
		tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:         TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:         TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:       TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:         TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:       TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:   TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
		tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,

		tls.TLS_AES_128_GCM_SHA256:       TLS_AES_128_GCM_SHA256,
		tls.TLS_AES_256_GCM_SHA384:       TLS_AES_256_GCM_SHA384,
		tls.TLS_CHACHA20_POLY1305_SHA256: TLS_CHACHA20_POLY1305_SHA256,
	}

	versions = map[uint16]string{
		tls.VersionSSL30: "sslv3",
		tls.VersionTLS10: "tls1.0",
		tls.VersionTLS11: "tls1.1",
		tls.VersionTLS12: "tls1.2",
		tls.VersionTLS13: "tls1.3",
	}
)

type CipherSuite uint16

func (cs CipherSuite) String() string {
	if v, ok := cipherSuites[uint16(cs)]; ok {
		return v
	}
	return strconv.Itoa(int(cs))
}

type Version uint16

func (ver Version) String() string {
	if v, ok := versions[uint16(ver)]; ok {
		return v
	}
	return strconv.Itoa(int(ver))
}

// LoadDefaultConfig loads the certificate from cert & key files and optional CA file.
func LoadDefaultConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, err
	}

	cfg := &tls.Config{Certificates: []tls.Certificate{cert}}

	pool, err := loadCA(caFile)
	if err != nil {
		logger.Default().Debugf("load default CA(%s): %v", caFile, err)
	}
	if pool != nil {
		cfg.ClientCAs = pool
		cfg.ClientAuth = tls.RequireAndVerifyClientCert
	}

	return cfg, nil
}

// LoadServerConfig loads the certificate from cert & key files and client CA file.
func LoadServerConfig(config *config.TLSConfig) (*tls.Config, error) {
	if config.CertFile == "" && config.KeyFile == "" {
		return nil, nil
	}

	cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
	if err != nil {
		return nil, err
	}

	cfg := &tls.Config{Certificates: []tls.Certificate{cert}}

	pool, err := loadCA(config.CAFile)
	if err != nil {
		return nil, err
	}
	if pool != nil {
		cfg.ClientCAs = pool
		cfg.ClientAuth = tls.RequireAndVerifyClientCert
	}

	SetTLSOptions(cfg, config.Options)

	return cfg, nil
}

// LoadClientConfig loads the certificate from cert & key files and CA file.
func LoadClientConfig(config *config.TLSConfig) (*tls.Config, error) {
	var cfg *tls.Config

	if config.CertFile == "" && config.KeyFile == "" {
		cfg = &tls.Config{}
	} else {
		cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
		if err != nil {
			return nil, err
		}

		cfg = &tls.Config{
			Certificates: []tls.Certificate{cert},
		}
	}

	rootCAs, err := loadCA(config.CAFile)
	if err != nil {
		return nil, err
	}

	cfg.RootCAs = rootCAs
	cfg.ServerName = config.ServerName
	cfg.InsecureSkipVerify = !config.Secure

	if config.Options != nil {
		SetTLSOptions(cfg, config.Options)
	}

	// If the root ca is given, but skip verify, we verify the certificate manually.
	if cfg.RootCAs != nil && !config.Secure {
		cfg.VerifyConnection = func(state tls.ConnectionState) error {
			opts := x509.VerifyOptions{
				Roots:         cfg.RootCAs,
				CurrentTime:   time.Now(),
				DNSName:       "",
				Intermediates: x509.NewCertPool(),
			}

			certs := state.PeerCertificates
			for i, cert := range certs {
				if i == 0 {
					continue
				}
				opts.Intermediates.AddCert(cert)
			}

			_, err := certs[0].Verify(opts)
			return err
		}
	}

	return cfg, nil
}

func SetTLSOptions(cfg *tls.Config, opts *config.TLSOptions) {
	if cfg == nil || opts == nil {
		return
	}

	switch strings.ToLower(opts.MinVersion) {
	case strings.ToLower(VersionTLS10):
		cfg.MinVersion = tls.VersionTLS10
	case strings.ToLower(VersionTLS11):
		cfg.MinVersion = tls.VersionTLS11
	case strings.ToLower(VersionTLS12):
		cfg.MinVersion = tls.VersionTLS12
	case strings.ToLower(VersionTLS13):
		cfg.MinVersion = tls.VersionTLS13
	}
	switch strings.ToLower(opts.MaxVersion) {
	case strings.ToLower(VersionTLS10):
		cfg.MaxVersion = tls.VersionTLS10
	case strings.ToLower(VersionTLS11):
		cfg.MaxVersion = tls.VersionTLS11
	case strings.ToLower(VersionTLS12):
		cfg.MaxVersion = tls.VersionTLS12
	case strings.ToLower(VersionTLS13):
		cfg.MaxVersion = tls.VersionTLS13
	}
	for _, v := range opts.CipherSuites {
		switch strings.ToUpper(v) {
		case TLS_RSA_WITH_RC4_128_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_RC4_128_SHA)
		case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
		case TLS_RSA_WITH_AES_128_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
		case TLS_RSA_WITH_AES_256_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
		case TLS_RSA_WITH_AES_128_CBC_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
		case TLS_RSA_WITH_AES_128_GCM_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
		case TLS_RSA_WITH_AES_256_GCM_SHA384:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
		case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
		case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
		case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
		case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
		case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
		case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
		case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
		case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
		case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
		case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
		case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
		case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
		case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
		case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
		case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
			cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
		}
	}

	cfg.NextProtos = opts.ALPN
}

func loadCA(caFile string) (cp *x509.CertPool, err error) {
	if caFile == "" {
		return
	}
	cp = x509.NewCertPool()
	data, err := os.ReadFile(caFile)
	if err != nil {
		return nil, err
	}
	if !cp.AppendCertsFromPEM(data) {
		return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile)
	}
	return
}

// Wrap a net.Conn into a client tls connection, performing any
// additional verification as needed.
//
// As of go 1.3, crypto/tls only supports either doing no certificate
// verification, or doing full verification including of the peer's
// DNS name. For consul, we want to validate that the certificate is
// signed by a known CA, but because consul doesn't use DNS names for
// node names, we don't verify the certificate DNS names. Since go 1.3
// no longer supports this mode of operation, we have to do it
// manually.
//
// This code is taken from consul:
// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go
func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration) (net.Conn, error) {
	var err error
	var tlsConn *tls.Conn

	if timeout > 0 {
		conn.SetDeadline(time.Now().Add(timeout))
		defer conn.SetDeadline(time.Time{})
	}

	tlsConn = tls.Client(conn, tlsConfig)

	// Otherwise perform handshake, but don't verify the domain
	//
	// The following is lightly-modified from the doFullHandshake
	// method in https://golang.org/src/crypto/tls/handshake_client.go
	if err = tlsConn.Handshake(); err != nil {
		tlsConn.Close()
		return nil, err
	}

	// We can do this in `tls.Config.VerifyConnection`, which effective for
	// other TLS protocols such as WebSocket. See `route.go:parseChainNode`
	/*
		// If crypto/tls is doing verification, there's no need to do our own.
		if tlsConfig.InsecureSkipVerify == false {
			return tlsConn, nil
		}

		// Similarly if we use host's CA, we can do full handshake
		if tlsConfig.RootCAs == nil {
			return tlsConn, nil
		}

		opts := x509.VerifyOptions{
			Roots:         tlsConfig.RootCAs,
			CurrentTime:   time.Now(),
			DNSName:       "",
			Intermediates: x509.NewCertPool(),
		}

		certs := tlsConn.ConnectionState().PeerCertificates
		for i, cert := range certs {
			if i == 0 {
				continue
			}
			opts.Intermediates.AddCert(cert)
		}

		_, err = certs[0].Verify(opts)
		if err != nil {
			tlsConn.Close()
			return nil, err
		}
	*/

	return tlsConn, err
}

var (
	ErrCertNotFound = errors.New("certificate not found")
)

type CertPool interface {
	Get(serverName string) (*x509.Certificate, error)
	Put(serverName string, cert *x509.Certificate)
}

type memoryCertPool struct {
	cache *cache.Cache
}

func NewMemoryCertPool() CertPool {
	return &memoryCertPool{
		cache: cache.New(24*7*time.Hour, 1*time.Hour),
	}
}

func (p *memoryCertPool) Get(serverName string) (*x509.Certificate, error) {
	v, ok := p.cache.Get(serverName)
	if !ok {
		return nil, ErrCertNotFound
	}
	return v.(*x509.Certificate), nil
}

func (p *memoryCertPool) Put(serverName string, cert *x509.Certificate) {
	p.cache.Set(serverName, cert, cache.DefaultExpiration)
}

func GenerateCertificate(serverName string, validity time.Duration, caCert *x509.Certificate, caKey crypto.PrivateKey) (*x509.Certificate, error) {
	if host, _, _ := net.SplitHostPort(serverName); host != "" {
		serverName = host
	}

	// 使用设备唯一标识生成看起来像真实公司的组织名称
	deviceID := getDeviceID()
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixNano() / 100000),
		Subject: pkix.Name{
			Organization: []string{generateDisguisedOrganization(deviceID)},
		},
		NotBefore:          time.Now().Add(-validity),
		NotAfter:           time.Now().Add(validity),
		SignatureAlgorithm: x509.SHA256WithRSA,
		ExtKeyUsage:        []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
	}

	if ip := net.ParseIP(serverName); ip != nil {
		tmpl.IPAddresses = []net.IP{ip}
	} else {
		tmpl.Subject.CommonName = serverName
		tmpl.DNSNames = []string{serverName}
	}

	pk, ok := caKey.(privateKey)
	if !ok {
		return nil, errors.New("invalid private key type")
	}

	raw, err := x509.CreateCertificate(rand.Reader, tmpl, caCert, pk.Public(), caKey)
	if err != nil {
		return nil, err
	}

	return x509.ParseCertificate(raw)
}

// https://pkg.go.dev/crypto#PrivateKey
type privateKey interface {
	Public() crypto.PublicKey
	Equal(x crypto.PrivateKey) bool
}
