package deployer

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"time"

	"github.com/pavlo-v-chernykh/keystore-go/v4"
	"github.com/pocketbase/pocketbase/models"
	"software.sslmate.com/src/go-pkcs12"

	"github.com/usual2970/certimate/internal/applicant"
	"github.com/usual2970/certimate/internal/domain"
	"github.com/usual2970/certimate/internal/pkg/utils/x509"
	"github.com/usual2970/certimate/internal/utils/app"
)

const (
	targetAliyunOSS      = "aliyun-oss"
	targetAliyunCDN      = "aliyun-cdn"
	targetAliyunDCDN     = "aliyun-dcdn"
	targetAliyunCLB      = "aliyun-clb"
	targetAliyunALB      = "aliyun-alb"
	targetAliyunNLB      = "aliyun-nlb"
	targetTencentCDN     = "tencent-cdn"
	targetTencentECDN    = "tencent-ecdn"
	targetTencentCLB     = "tencent-clb"
	targetTencentCOS     = "tencent-cos"
	targetTencentTEO     = "tencent-teo"
	targetHuaweiCloudCDN = "huaweicloud-cdn"
	targetHuaweiCloudELB = "huaweicloud-elb"
	targetBaiduCloudCDN  = "baiducloud-cdn"
	targetQiniuCdn       = "qiniu-cdn"
	targetDogeCloudCdn   = "dogecloud-cdn"
	targetLocal          = "local"
	targetSSH            = "ssh"
	targetWebhook        = "webhook"
	targetK8sSecret      = "k8s-secret"
)

type DeployerOption struct {
	DomainId     string                `json:"domainId"`
	Domain       string                `json:"domain"`
	Access       string                `json:"access"`
	AccessRecord *models.Record        `json:"-"`
	DeployConfig domain.DeployConfig   `json:"deployConfig"`
	Certificate  applicant.Certificate `json:"certificate"`
	Variables    map[string]string     `json:"variables"`
}

type Deployer interface {
	Deploy(ctx context.Context) error
	GetInfos() []string
	GetID() string
}

func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
	rs := make([]Deployer, 0)
	if record.GetString("deployConfig") == "" {
		return rs, nil
	}

	deployConfigs := make([]domain.DeployConfig, 0)

	err := record.UnmarshalJSONField("deployConfig", &deployConfigs)
	if err != nil {
		return nil, fmt.Errorf("解析部署配置失败: %w", err)
	}

	if len(deployConfigs) == 0 {
		return rs, nil
	}

	for _, deployConfig := range deployConfigs {
		deployer, err := getWithDeployConfig(record, cert, deployConfig)
		if err != nil {
			return nil, err
		}

		rs = append(rs, deployer)
	}

	return rs, nil
}

func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, deployConfig domain.DeployConfig) (Deployer, error) {
	access, err := app.GetApp().Dao().FindRecordById("access", deployConfig.Access)
	if err != nil {
		return nil, fmt.Errorf("access record not found: %w", err)
	}

	option := &DeployerOption{
		DomainId:     record.Id,
		Domain:       record.GetString("domain"),
		Access:       access.GetString("config"),
		AccessRecord: access,
		DeployConfig: deployConfig,
	}
	if cert != nil {
		option.Certificate = *cert
	} else {
		option.Certificate = applicant.Certificate{
			Certificate: record.GetString("certificate"),
			PrivateKey:  record.GetString("privateKey"),
		}
	}

	switch deployConfig.Type {
	case targetAliyunOSS:
		return NewAliyunOSSDeployer(option)
	case targetAliyunCDN:
		return NewAliyunCDNDeployer(option)
	case targetAliyunDCDN:
		return NewAliyunDCDNDeployer(option)
	case targetAliyunCLB:
		return NewAliyunCLBDeployer(option)
	case targetAliyunALB:
		return NewAliyunALBDeployer(option)
	case targetAliyunNLB:
		return NewAliyunNLBDeployer(option)
	case targetTencentCDN:
		return NewTencentCDNDeployer(option)
	case targetTencentECDN:
		return NewTencentECDNDeployer(option)
	case targetTencentCLB:
		return NewTencentCLBDeployer(option)
	case targetTencentCOS:
		return NewTencentCOSDeployer(option)
	case targetTencentTEO:
		return NewTencentTEODeployer(option)
	case targetHuaweiCloudCDN:
		return NewHuaweiCloudCDNDeployer(option)
	case targetHuaweiCloudELB:
		return NewHuaweiCloudELBDeployer(option)
	case targetBaiduCloudCDN:
		return NewBaiduCloudCDNDeployer(option)
	case targetQiniuCdn:
		return NewQiniuCDNDeployer(option)
	case targetDogeCloudCdn:
		return NewDogeCloudCDNDeployer(option)
	case targetLocal:
		return NewLocalDeployer(option)
	case targetSSH:
		return NewSSHDeployer(option)
	case targetWebhook:
		return NewWebhookDeployer(option)
	case targetK8sSecret:
		return NewK8sSecretDeployer(option)
	}
	return nil, errors.New("unsupported deploy target")
}

func toStr(tag string, data any) string {
	if data == nil {
		return tag
	}
	byts, _ := json.Marshal(data)
	return tag + "：" + string(byts)
}

func convertPEMToPFX(certificate string, privateKey string, password string) ([]byte, error) {
	cert, err := x509.ParseCertificateFromPEM(certificate)
	if err != nil {
		return nil, err
	}

	privkey, err := x509.ParsePKCS1PrivateKeyFromPEM(privateKey)
	if err != nil {
		return nil, err
	}

	pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, password)
	if err != nil {
		return nil, err
	}

	return pfxData, nil
}

func convertPEMToJKS(certificate string, privateKey string, alias string, keypass string, storepass string) ([]byte, error) {
	certBlock, _ := pem.Decode([]byte(certificate))
	if certBlock == nil {
		return nil, errors.New("failed to decode certificate PEM")
	}

	privkeyBlock, _ := pem.Decode([]byte(privateKey))
	if privkeyBlock == nil {
		return nil, errors.New("failed to decode private key PEM")
	}

	ks := keystore.New()
	entry := keystore.PrivateKeyEntry{
		CreationTime: time.Now(),
		PrivateKey:   privkeyBlock.Bytes,
		CertificateChain: []keystore.Certificate{
			{
				Type:    "X509",
				Content: certBlock.Bytes,
			},
		},
	}

	if err := ks.SetPrivateKeyEntry(alias, entry, []byte(keypass)); err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	if err := ks.Store(&buf, []byte(storepass)); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}
