package pki

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"

	"git.paulbsd.com/paulbsd/pki/src/cert"
	"git.paulbsd.com/paulbsd/pki/src/config"
	"git.paulbsd.com/paulbsd/pki/src/domain"
	"github.com/go-acme/lego/v4/certcrypto"
	"github.com/go-acme/lego/v4/certificate"
	"github.com/go-acme/lego/v4/challenge"
	"github.com/go-acme/lego/v4/lego"
	"github.com/go-acme/lego/v4/registration"
)

// Init initialize user table
func (u *User) Init(cfg *config.Config) (err error) {
	if cfg.Switchs.Init {
		err = u.CreateInitialUser(cfg)
		return
	}
	return
}

// GetEntry returns requested acme ressource in database relative to domain
func (u *User) GetEntry(cfg *config.Config, domain *string) (Entry cert.Entry, err error) {
	has, err := cfg.Db.Where("domain = ?", domain).And(
		"auth_url = ?", cfg.ACME.AuthURL).And(
		fmt.Sprintf("validity_end::timestamp-'%d DAY'::INTERVAL >= now()", cfg.ACME.MaxDaysBefore)).Desc(
		"id").Get(&Entry)

	if !has {
		err = fmt.Errorf("entry doesn't exists")
	}

	return
}

// HandleRegistration get existing registration if exists, else fetch a new registration
func (u *User) HandleRegistration(cfg *config.Config, client *lego.Client) (err error) {
	reg, err := client.Registration.ResolveAccountByKey()
	if err != nil {
		log.Println(u.key, reg, err)
	} else {
		u.Registration = reg
		return
	}

	reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
	if err != nil {
		log.Println(err)
	}
	u.Registration = reg

	// Saving private key
	u.SavePrivateKey()
	_, err = cfg.Db.Update(u)
	return
}

// RequestNewCert returns a newly requested certificate to letsencrypt
func (u *User) RequestNewCert(cfg *config.Config, domainname *string) (certs *certificate.Resource, err error) {
	legoconfig := lego.NewConfig(u)
	legoconfig.CADirURL = cfg.ACME.AuthURL
	legoconfig.Certificate.KeyType = certcrypto.RSA2048

	dom := domain.Domain{Domain: *domainname}
	_, err = cfg.Db.Get(&dom)
	if err != nil {
		log.Println(err)
	}

	var provider challenge.Provider

	switch dom.Provider {
	case "ovh":
		provider, err = initOVHProvider(cfg)
	case "pdns":
		provider, err = initPowerDNSProvider(cfg)
	default:
		return
	}
	if err != nil {
		log.Println(err)
	}

	client, err := lego.NewClient(legoconfig)
	if err != nil {
		log.Println(err)
	}

	err = client.Challenge.SetDNS01Provider(provider)
	if err != nil {
		log.Println(err)
	}

	// If PKICtx doesn't exists, get existing of fetch registration
	if u.Registration == nil {
		err = u.HandleRegistration(cfg, client)
		if err != nil {
			log.Println(err)
		}
	}

	request := certificate.ObtainRequest{
		Domains: []string{*domainname, fmt.Sprintf(`*.%s`, *domainname)},
		Bundle:  true,
	}

	certs, err = client.Certificate.Obtain(request)
	if err != nil {
		log.Println(err)
	}

	return
}

// GetEmail returns User Email
func (u *User) GetEmail() string {
	return u.Email
}

// GetRegistration returns User Registration
func (u *User) GetRegistration() *registration.Resource {
	return u.Registration
}

// GetPrivateKey returns User key
func (u *User) GetPrivateKey() crypto.PrivateKey {
	if u.key == nil {
		if u.PrivateKey == "" {
			u.key, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
			return u.key
		}
		block, _ := pem.Decode([]byte(u.PrivateKey))
		switch block.Type {
		case "RSA PRIVATE KEY":
			u.key, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
		case "EC PRIVATE KEY":
			u.key, _ = x509.ParseECPrivateKey(block.Bytes)
		}
		return u.key
	}
	return u.key
}

// SavePrivateKey returns User key
func (u *User) SavePrivateKey() (err error) {
	if u.key != nil {
		pemKey := certcrypto.PEMBlock(u.key)
		u.PrivateKey = string(pem.EncodeToMemory(pemKey))
	}
	return
}

/*
// ReadConfig reads accounting config to file
func (u *User) ReadConfig(cfg *config.Config, filename string) (err error) {
	f, err := os.Open(filename)
	defer f.Close()
	if err != nil {
		return
	}
	d, err := ioutil.ReadAll(f)
	err = json.Unmarshal(d, u)
	return
}

// WriteConfig writes accounting config to file
func (u *User) WriteConfig(filename string) (err error) {
	file, err := json.MarshalIndent(u, "", " ")
	if err != nil {
		return
	}
	err = ioutil.WriteFile(filename, file, 0644)
	return
}*/
