package agent

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"math/big"
	"strings"
	"time"
)

const (
	CertsDNSName = "godoxy.agent"
)

func toPEMPair(certDER []byte, key *ecdsa.PrivateKey) *PEMPair {
	marshaledKey, err := marshalECPrivateKey(key)
	if err != nil {
		// This is a critical internal error during PEM encoding of a newly generated key.
		// Panicking is acceptable here as it indicates a fundamental issue.
		panic(fmt.Sprintf("failed to marshal EC private key for PEM encoding: %v", err))
	}
	return &PEMPair{
		Cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
		Key:  pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: marshaledKey}),
	}
}

func marshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
	derBytes, err := x509.MarshalECPrivateKey(key)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal EC private key: %w", err)
	}
	return derBytes, nil
}

func b64Encode(data []byte) string {
	return base64.StdEncoding.EncodeToString(data)
}

func b64Decode(data string) ([]byte, error) {
	return base64.StdEncoding.DecodeString(data)
}

type PEMPair struct {
	Cert, Key []byte
}

func (p *PEMPair) String() string {
	return b64Encode(p.Cert) + ";" + b64Encode(p.Key)
}

func (p *PEMPair) Load(data string) (err error) {
	parts := strings.Split(data, ";")
	if len(parts) != 2 {
		return errors.New("invalid PEM pair")
	}
	p.Cert, err = b64Decode(parts[0])
	if err != nil {
		return err
	}
	p.Key, err = b64Decode(parts[1])
	if err != nil {
		return err
	}
	return nil
}

func (p *PEMPair) Encrypt(encKey []byte) (PEMPair, error) {
	cert, err := encrypt(p.Cert, encKey)
	if err != nil {
		return PEMPair{}, err
	}
	key, err := encrypt(p.Key, encKey)
	if err != nil {
		return PEMPair{}, err
	}
	return PEMPair{Cert: cert, Key: key}, nil
}

func (p *PEMPair) Decrypt(encKey []byte) (PEMPair, error) {
	cert, err := decrypt(p.Cert, encKey)
	if err != nil {
		return PEMPair{}, err
	}
	key, err := decrypt(p.Key, encKey)
	if err != nil {
		return PEMPair{}, err
	}
	return PEMPair{Cert: cert, Key: key}, nil
}

func encrypt(data []byte, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, err
	}
	return gcm.Seal(nonce, nonce, data, nil), nil
}

func decrypt(data []byte, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}

	nonce := data[:gcm.NonceSize()]
	ciphertext := data[gcm.NonceSize():]
	return gcm.Open(nil, nonce, ciphertext, nil)
}

func (p *PEMPair) ToTLSCert() (*tls.Certificate, error) {
	cert, err := tls.X509KeyPair(p.Cert, p.Key)
	return &cert, err
}

func newSerialNumber() (*big.Int, error) {
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) // 128-bit random number
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, fmt.Errorf("failed to generate serial number: %w", err)
	}
	return serialNumber, nil
}

func NewAgent() (ca, srv, client *PEMPair, err error) {
	caSerialNumber, err := newSerialNumber()
	if err != nil {
		return nil, nil, nil, err
	}
	// Create the CA's certificate
	caTemplate := &x509.Certificate{
		SerialNumber: caSerialNumber,
		Subject: pkix.Name{
			Organization: []string{"GoDoxy"},
			CommonName:   CertsDNSName,
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1000, 0, 0), // 1000 years
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
		MaxPathLen:            0,
		MaxPathLenZero:        true,
		SignatureAlgorithm:    x509.ECDSAWithSHA256,
	}

	caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, nil, err
	}

	caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
	if err != nil {
		return nil, nil, nil, err
	}

	ca = toPEMPair(caDER, caKey)

	// Generate a new private key for the server certificate
	serverKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, nil, err
	}

	serverSerialNumber, err := newSerialNumber()
	if err != nil {
		return nil, nil, nil, err
	}
	srvTemplate := &x509.Certificate{
		SerialNumber: serverSerialNumber,
		Issuer:       caTemplate.Subject,
		Subject: pkix.Name{
			Organization:       caTemplate.Subject.Organization,
			OrganizationalUnit: []string{"Server"},
			CommonName:         CertsDNSName,
		},
		DNSNames:           []string{CertsDNSName},
		NotBefore:          time.Now(),
		NotAfter:           time.Now().AddDate(1000, 0, 0), // Add validity period
		KeyUsage:           x509.KeyUsageDigitalSignature,
		ExtKeyUsage:        []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		SignatureAlgorithm: x509.ECDSAWithSHA256,
	}

	srvCertDER, err := x509.CreateCertificate(rand.Reader, srvTemplate, caTemplate, &serverKey.PublicKey, caKey)
	if err != nil {
		return nil, nil, nil, err
	}

	srv = toPEMPair(srvCertDER, serverKey)

	clientKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, nil, err
	}

	clientSerialNumber, err := newSerialNumber()
	if err != nil {
		return nil, nil, nil, err
	}
	clientTemplate := &x509.Certificate{
		SerialNumber: clientSerialNumber,
		Issuer:       caTemplate.Subject,
		Subject: pkix.Name{
			Organization:       caTemplate.Subject.Organization,
			OrganizationalUnit: []string{"Client"},
			CommonName:         CertsDNSName,
		},
		DNSNames:           []string{CertsDNSName},
		NotBefore:          time.Now(),
		NotAfter:           time.Now().AddDate(1000, 0, 0),
		KeyUsage:           x509.KeyUsageDigitalSignature,
		ExtKeyUsage:        []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		SignatureAlgorithm: x509.ECDSAWithSHA256,
	}
	clientCertDER, err := x509.CreateCertificate(rand.Reader, clientTemplate, caTemplate, &clientKey.PublicKey, caKey)
	if err != nil {
		return nil, nil, nil, err
	}

	client = toPEMPair(clientCertDER, clientKey)
	return ca, srv, client, err
}
