package services

import (
	"bytes"
	"encoding/json"
	"net/http"
	"sync"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
	"github.com/go-pg/pg/v10"
	"github.com/pkg/errors"
)

var (
	certService CertServiceInterface
	certOnce    sync.Once
)

func GetCertService() CertServiceInterface {
	certOnce.Do(func() {
		if certService != nil {
			return
		}
		certService = NewCertService()
	})

	return certService
}

func SetCertService(cs CertServiceInterface) {
	certService = cs
}

func NewCertService() CertServiceInterface {
	return &CertService{
		certURL:      envtool.GetEnv("CERT_URL", "REPLACE_ME"),
		certAPIToken: envtool.GetEnv("CERTIFICATE_API_KEY", "REPLACE_ME"),
	}
}

type CertServiceInterface interface {
	CheckCertificateNeedsRenewal(vin string, certType string) (bool, error)
	RenewCertificate(vin string, certType string) (*common.Certificate, error)
}

type CertService struct {
	certURL      string
	certAPIToken string
}

func (cs *CertService) CheckCertificateNeedsRenewal(vin string, certType string) (bool, error) {
	logger.Debug().Msg("checking " + certType + " cert for vin " + vin)

	cert, err := GetDB().GetCertificates().SelectMostRecent(vin, certType)
	if err != nil && !errors.Is(err, pg.ErrNoRows) {
		return false, err
	}
	if cert == nil {
		logger.Debug().Msg("no existing " + certType + " cert to renew for vin " + vin)
		return false, nil
	}

	var daysBeforeExp = 183 // six months
	switch certType {
	case common.CertTBOX:
		daysBeforeExp = envtool.GetEnvInt("TBOX_CERT_RENEW_DAYS_BEFORE_EXP", daysBeforeExp)
	case common.CertICC:
		daysBeforeExp = envtool.GetEnvInt("ICC_CERT_RENEW_DAYS_BEFORE_EXP", daysBeforeExp)
	}

	logger.Debug().Msg("checking validity of " + certType + " cert with serial number " + cert.SerialNumber + " for vin " + vin)

	return cert.IsExpiredOrInvalidAtTime(time.Now(), daysBeforeExp)
}

func (cs *CertService) RenewCertificate(vin string, certType string) (*common.Certificate, error) {
	logger.Debug().Msg("renewing " + certType + " cert for vin " + vin)

	jsonBytes, err := json.Marshal(common.CertificateRenewRequest{
		Type:       certType,
		CommonName: vin,
	})
	if err != nil {
		return nil, err
	}

	request, err := http.NewRequest(http.MethodPost, cs.certURL+"renew", bytes.NewReader(jsonBytes))
	if err != nil {
		return nil, err
	}
	request.Header.Add("Api-Key", cs.certAPIToken)

	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	if resp.StatusCode != http.StatusOK {
		return nil, errors.New("renew " + certType + " certificate returned " + resp.Status + " for vin " + vin)
	}

	defer resp.Body.Close()

	var cert common.Certificate
	err = json.NewDecoder(resp.Body).Decode(&cert)
	if err != nil {
		return nil, err
	}

	return &cert, err
}
