package certs

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"time"

	"gitea.benny.dog/torjus/ezshare/store"
)

type CertService struct {
	caCert *x509.Certificate
	caKey  crypto.Signer
	store  store.CertificateStore
}

func NewCertService(s store.CertificateStore, certBytes, keyBytes []byte) (*CertService, error) {
	// Try to decode key as PEM
	keyBlock, _ := pem.Decode(keyBytes)
	if keyBlock != nil {
		if keyBlock.Type != "EC PRIVATE KEY" {
			return nil, fmt.Errorf("private key is not of type EC PRIVATE KEY: %s", keyBlock.Type)
		}
		keyBytes = keyBlock.Bytes
	}
	// Try to decode cert as PEM
	certBlock, _ := pem.Decode(certBytes)
	if certBlock != nil {
		if certBlock.Type != "CERTIFICATE" {
			return nil, fmt.Errorf("certificate is not of type CERTIFICATE: %s", certBlock.Type)
		}
		certBytes = certBlock.Bytes
	}

	caCert, err := x509.ParseCertificate(certBytes)
	if err != nil {
		return nil, fmt.Errorf("unable to parse certificate: %w", err)
	}

	if !caCert.IsCA {
		return nil, fmt.Errorf("certificate is not CA")
	}

	caKey, err := x509.ParseECPrivateKey(keyBytes)
	if err != nil {
		return nil, fmt.Errorf("unable to parse private key: %w", err)
	}

	return &CertService{caCert: caCert, caKey: caKey, store: s}, nil
}

func (cs *CertService) NewClient(id string) ([]byte, []byte, error) {
	cert := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixMilli()),
		Subject: pkix.Name{
			CommonName:   id,
			Organization: []string{"ezshare"},
			Country:      []string{"No"},
			Locality:     []string{"Oslo"},
		},
		NotBefore:   time.Now(),
		NotAfter:    time.Now().AddDate(0, 0, 30),
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:    x509.KeyUsageDigitalSignature,
	}

	certPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, err
	}
	certPrivKeyBytes, err := x509.MarshalECPrivateKey(certPrivKey)
	if err != nil {
		return nil, nil, err
	}

	certBytes, err := x509.CreateCertificate(rand.Reader, cert, cs.caCert, &certPrivKey.PublicKey, cs.caKey)
	if err != nil {
		return nil, nil, err
	}

	keyPEM := new(bytes.Buffer)
	if err := pem.Encode(keyPEM, &pem.Block{
		Type:  "EC PRIVATE KEY",
		Bytes: certPrivKeyBytes,
	}); err != nil {
		return nil, nil, fmt.Errorf("unable to encode client private key: %w", err)
	}
	certPEM := new(bytes.Buffer)
	if err := pem.Encode(certPEM, &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: certBytes,
	}); err != nil {
		return nil, nil, fmt.Errorf("unable to encode client private key: %w", err)
	}

	signed, err := x509.ParseCertificate(certBytes)
	if err != nil {
		return nil, nil, err
	}

	if err := cs.store.StoreCertificate(signed); err != nil {
		return nil, nil, err
	}

	return certPEM.Bytes(), keyPEM.Bytes(), nil
}

func (cs *CertService) VerifyClient(certBytes []byte) (string, error) {

	// Try to decode cert as PEM
	certBlock, _ := pem.Decode(certBytes)
	if certBlock != nil {
		if certBlock.Type != "CERTIFICATE" {
			return "", fmt.Errorf("certificate is not of type CERTIFICATE: %s", certBlock.Type)
		}
		certBytes = certBlock.Bytes
	}

	cert, err := x509.ParseCertificate(certBytes)
	if err != nil {
		return "", fmt.Errorf("unable to parse certificate: %w", err)
	}

	rootPool := x509.NewCertPool()
	rootPool.AddCert(cs.caCert)

	if _, err := cert.Verify(x509.VerifyOptions{
		Roots: rootPool,
	}); err != nil {
		return "", fmt.Errorf("unable to verify: %w", err)
	}

	revoked, err := cs.store.IsRevoked(cert.SerialNumber.String())
	if err != nil {
		return "", fmt.Errorf("unable to check if revoked: %w", err)
	}
	if revoked {
		return "", fmt.Errorf("certificate is revoked")
	}

	return cert.Subject.CommonName, nil
}