package pkiws

import (
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"
	"regexp"
	"time"

	"git.paulbsd.com/paulbsd/pki/src/cert"
	"git.paulbsd.com/paulbsd/pki/src/config"
	"git.paulbsd.com/paulbsd/pki/src/pki"
)

// GetCertificate get certificate from database if exists, of request it from ACME
func GetCertificate(cfg *config.Config, user *pki.User, domain string) (result EntryResponse, err error) {
	err = CheckDomain(domain)
	if err != nil {
		return result, err
	}

	entry, err := user.GetEntry(cfg, domain)
	if err != nil {
		certs, err := user.RequestNewCert(cfg, domain)
		if err != nil {
			log.Println(fmt.Sprintf("Error fetching new certificate %s", err))
			return result, err
		}
		NotBefore, NotAfter, err := GetDates(certs.Certificate)
		if err != nil {
			log.Println("Error where parsing dates")
			return result, err
		}
		entry := cert.Entry{Domain: domain,
			Certificate:   string(certs.Certificate),
			PrivateKey:    string(certs.PrivateKey),
			ValidityBegin: NotBefore,
			ValidityEnd:   NotAfter,
			AuthURL:       cfg.ACME.AuthURL}
		cfg.Db.Insert(entry)
		result = convertEntryToResponse(entry)
		return result, err
	}
	result = convertEntryToResponse(entry)
	return
}

// CheckDomain check if requested domain is valid
func CheckDomain(domain string) (err error) {
	res, err := regexp.Match(`^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`, []byte(domain))
	if !res {
		return fmt.Errorf("Domain has not a valid syntax")
	}
	return
}

// GetDates decodes NotBefore and NotAfter date of cert
func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error) {
	block, _ := pem.Decode(cert)
	if block.Type == "CERTIFICATE" {
		ce, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			log.Fatal("Error when parsing certificate")
		}
		NotBefore = ce.NotBefore
		NotAfter = ce.NotAfter
	}
	return
}

// NeedRenewal is an unimplemented method
func NeedRenewal(cfg config.Config) (res bool, err error) {
	return
}

// convertEntryToResponse converts database ACME entry to JSON ACME entry
func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
	out.Domain = in.Domain
	out.Certificate = in.Certificate
	out.PrivateKey = in.PrivateKey
	out.ValidityBegin = in.ValidityBegin
	out.ValidityEnd = in.ValidityEnd

	return
}

// EntryResponse is the struct defining JSON response from webservice
type EntryResponse struct {
	Domain        string    `json:"domain"`
	Certificate   string    `json:"certificate"`
	PrivateKey    string    `json:"privatekey"`
	ValidityBegin time.Time `json:"validitybegin"`
	ValidityEnd   time.Time `json:"validityend"`
}
