package ssl

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/buserr"
	"golang.org/x/crypto/acme"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/model"
	"github.com/go-acme/lego/v4/certcrypto"
	"github.com/go-acme/lego/v4/lego"
	"github.com/go-acme/lego/v4/registration"
)

var Orders = make(map[uint]*acme.Order)

type domainError struct {
	Domain string
	Error  error
}

type zeroSSLRes struct {
	Success    bool   `json:"success"`
	EabKid     string `json:"eab_kid"`
	EabHmacKey string `json:"eab_hmac_key"`
}

type KeyType = certcrypto.KeyType

const (
	KeyEC256   = certcrypto.EC256
	KeyEC384   = certcrypto.EC384
	KeyRSA2048 = certcrypto.RSA2048
	KeyRSA3072 = certcrypto.RSA3072
	KeyRSA4096 = certcrypto.RSA4096
)

type AcmeUser struct {
	Email        string
	Registration *registration.Resource
	Key          crypto.PrivateKey
}

func (u *AcmeUser) GetEmail() string {
	return u.Email
}

func (u *AcmeUser) GetRegistration() *registration.Resource {
	return u.Registration
}
func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
	return u.Key
}

func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) {
	var (
		marshal []byte
		block   *pem.Block
		err     error
	)

	switch keyType {
	case KeyEC256, KeyEC384:
		key := priKey.(*ecdsa.PrivateKey)
		marshal, err = x509.MarshalECPrivateKey(key)
		if err != nil {
			return nil, err
		}
		block = &pem.Block{
			Type:  "EC PRIVATE KEY",
			Bytes: marshal,
		}
	case KeyRSA2048, KeyRSA3072, KeyRSA4096:
		key := priKey.(*rsa.PrivateKey)
		marshal = x509.MarshalPKCS1PrivateKey(key)
		block = &pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: marshal,
		}
	}

	return pem.EncodeToMemory(block), nil
}

func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) {
	var (
		priKey crypto.PrivateKey
		err    error
	)

	if acmeAccount.PrivateKey != "" {
		switch KeyType(acmeAccount.KeyType) {
		case KeyEC256, KeyEC384:
			block, _ := pem.Decode([]byte(acmeAccount.PrivateKey))
			priKey, err = x509.ParseECPrivateKey(block.Bytes)
			if err != nil {
				return nil, err
			}
		case KeyRSA2048, KeyRSA3072, KeyRSA4096:
			block, _ := pem.Decode([]byte(acmeAccount.PrivateKey))
			priKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
			if err != nil {
				return nil, err
			}
		}

	} else {
		priKey, err = certcrypto.GeneratePrivateKey(KeyType(acmeAccount.KeyType))
		if err != nil {
			return nil, err
		}
	}

	myUser := &AcmeUser{
		Email: acmeAccount.Email,
		Key:   priKey,
	}
	config := NewConfigWithProxy(myUser, acmeAccount.Type, acmeAccount.CaDirURL, proxy)
	client, err := lego.NewClient(config)
	if err != nil {
		return nil, err
	}
	var reg *registration.Resource
	if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" {
		if acmeAccount.Type == "zerossl" {
			var res *zeroSSLRes
			res, err = getZeroSSLEabCredentials(acmeAccount.Email)
			if err != nil {
				return nil, err
			}
			if res.Success {
				acmeAccount.EabKid = res.EabKid
				acmeAccount.EabHmacKey = res.EabHmacKey
			} else {
				return nil, fmt.Errorf("get zero ssl eab credentials failed")
			}
		}

		eabOptions := registration.RegisterEABOptions{
			TermsOfServiceAgreed: true,
			Kid:                  acmeAccount.EabKid,
			HmacEncoded:          acmeAccount.EabHmacKey,
		}
		reg, err = client.Registration.RegisterWithExternalAccountBinding(eabOptions)
		if err != nil {
			return nil, err
		}
	} else {
		reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
		if err != nil {
			return nil, err
		}
	}
	myUser.Registration = reg

	acmeClient := &AcmeClient{
		User:   myUser,
		Client: client,
		Config: config,
	}

	return acmeClient, nil
}

func getCaDirURL(accountType, customCaURL string) string {
	var caDirURL string
	switch accountType {
	case "letsencrypt":
		caDirURL = "https://acme-v02.api.letsencrypt.org/directory"
	case "zerossl":
		caDirURL = "https://acme.zerossl.com/v2/DV90"
	case "buypass":
		caDirURL = "https://api.buypass.com/acme/directory"
	case "google":
		caDirURL = "https://dv.acme-v02.api.pki.goog/directory"
	case "freessl":
		caDirURL = "https://acmepro.freessl.cn/v2/DV"
	case "custom":
		caDirURL = customCaURL
	}
	return caDirURL
}

func NewConfigWithProxy(user registration.User, accountType, customCaURL string, systemProxy *dto.SystemProxy) *lego.Config {
	var (
		caDirURL      string
		proxyURL      string
		proxyUser     string
		proxyPassword string
	)
	caDirURL = getCaDirURL(accountType, customCaURL)
	if systemProxy != nil {
		proxyURL = fmt.Sprintf("%s://%s:%s", systemProxy.Type, systemProxy.URL, systemProxy.Port)
		proxyUser = systemProxy.User
		proxyPassword = systemProxy.Password
	}
	return &lego.Config{
		CADirURL:   caDirURL,
		UserAgent:  "1Panel",
		User:       user,
		HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword),
		Certificate: lego.CertificateConfig{
			KeyType: certcrypto.RSA2048,
			Timeout: 60 * time.Second,
		},
	}
}

func initCertPool() *x509.CertPool {
	customCACertsPath := os.Getenv("LEGO_CA_CERTIFICATES")
	if customCACertsPath == "" {
		return nil
	}

	useSystemCertPool, _ := strconv.ParseBool(os.Getenv("LEGO_CA_SYSTEM_CERT_POOL"))

	caCerts := strings.Split(customCACertsPath, string(os.PathListSeparator))

	certPool, err := lego.CreateCertPool(caCerts, useSystemCertPool)
	if err != nil {
		panic(fmt.Sprintf("create certificates pool: %v", err))
	}

	return certPool
}

func createHTTPClientWithProxy(proxyURL, username, password string) *http.Client {
	var proxyFunc func(*http.Request) (*url.URL, error)
	if proxyURL != "" {
		parsedURL, err := url.Parse(proxyURL)
		if err != nil {
			proxyFunc = http.ProxyFromEnvironment
		} else {
			if username != "" && password != "" {
				parsedURL.User = url.UserPassword(username, password)
			} else if username != "" {
				parsedURL.User = url.User(username)
			}
			proxyFunc = http.ProxyURL(parsedURL)
		}
	} else {
		proxyFunc = func(_ *http.Request) (*url.URL, error) {
			return nil, nil
		}
	}

	return &http.Client{
		Timeout: 2 * time.Minute,
		Transport: &http.Transport{
			Proxy: proxyFunc,
			DialContext: (&net.Dialer{
				Timeout:   60 * time.Second,
				KeepAlive: 60 * time.Second,
			}).DialContext,
			TLSHandshakeTimeout:   60 * time.Second,
			ResponseHeaderTimeout: 60 * time.Second,
			TLSClientConfig: &tls.Config{
				ServerName: os.Getenv("LEGO_CA_SERVER_NAME"),
				RootCAs:    initCertPool(),
			},
		},
	}
}

func getZeroSSLEabCredentials(email string) (*zeroSSLRes, error) {
	baseURL := "https://api.zerossl.com/acme/eab-credentials-email"
	params := url.Values{}
	params.Add("email", email)
	requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())

	req, err := http.NewRequest("POST", requestURL, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	defer client.CloseIdleConnections()
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned non-200 status: %d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
	}

	var result zeroSSLRes
	err = json.Unmarshal(body, &result)
	if err != nil {
		return nil, err
	}

	return &result, nil
}

func GetPrivateKeyByType(keyType, sslPrivateKey string) (crypto.PrivateKey, error) {
	var (
		privateKey crypto.PrivateKey
		err        error
	)
	kType := KeyType(keyType)
	if sslPrivateKey == "" {
		privateKey, err = certcrypto.GeneratePrivateKey(kType)
		if err != nil {
			return nil, err
		}
		return privateKey, nil
	}
	block, _ := pem.Decode([]byte(sslPrivateKey))
	if block == nil {
		return nil, buserr.New("invalid PEM block")
	}
	var privKey crypto.PrivateKey
	switch kType {
	case certcrypto.EC256, certcrypto.EC384:
		privKey, err = x509.ParseECPrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
	case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096:
		privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return nil, err
		}
	}
	privateKey = privKey
	return privateKey, nil
}

func getWebsiteSSLDomains(websiteSSL *model.WebsiteSSL) []string {
	domains := []string{websiteSSL.PrimaryDomain}
	if websiteSSL.Domains != "" {
		domains = append(domains, strings.Split(websiteSSL.Domains, ",")...)
	}
	return domains
}
