package tool

import (
    "crypto"
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/tls"
    "crypto/x509"
    "github.com/0xJacky/Nginx-UI/server/model"
    "github.com/0xJacky/Nginx-UI/server/settings"
    "github.com/go-acme/lego/v4/certcrypto"
    "github.com/go-acme/lego/v4/certificate"
    "github.com/go-acme/lego/v4/challenge/http01"
    "github.com/go-acme/lego/v4/lego"
    "github.com/go-acme/lego/v4/registration"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "time"
)

// MyUser You'll need a user or account type that implements acme.User
type MyUser struct {
    Email        string
    Registration *registration.Resource
    key          crypto.PrivateKey
}

func (u *MyUser) GetEmail() string {
    return u.Email
}
func (u MyUser) GetRegistration() *registration.Resource {
    return u.Registration
}
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
    return u.key
}

func AutoCert() {
    for {
        log.Println("[AutoCert] Start")
        autoCertList := model.GetAutoCertList()
        for i := range autoCertList {
            domain := autoCertList[i].Domain
            key := GetCertInfo(domain)
            // 未到一个月
            if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
                continue
            }
            // 过一个月了
            err := IssueCert(domain)
            if err != nil {
                log.Println(err)
            }
        }
        time.Sleep(1 * time.Hour)
    }
}

func GetCertInfo(domain string) (key *x509.Certificate) {
    ts := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }

    client := &http.Client{Transport: ts}

    response, err := client.Get("https://" + domain)

    if err != nil {
        return
    }

    defer func(Body io.ReadCloser) {
        err = Body.Close()
        if err != nil {
            log.Println(err)
            return
        }
    }(response.Body)

    key = response.TLS.PeerCertificates[0]

    return
}

func IssueCert(domain string) error {
    // Create a user. New accounts need an email and private key to start.
    privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    if err != nil {
        log.Println(err)
        return err
    }

    myUser := MyUser{
        Email: settings.ServerSettings.Email,
        key:   privateKey,
    }

    config := lego.NewConfig(&myUser)

    if settings.ServerSettings.Demo {
        config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
    }
    config.Certificate.KeyType = certcrypto.RSA2048

    // A client facilitates communication with the CA server.
    client, err := lego.NewClient(config)
    if err != nil {
        log.Println(err)
        return err
    }

    err = client.Challenge.SetHTTP01Provider(
        http01.NewProviderServer("",
            settings.ServerSettings.HTTPChallengePort,
        ),
    )
    if err != nil {
        log.Println(err)
        return err
    }

    // New users will need to register
    reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
    if err != nil {
        log.Println(err)
        return err
    }
    myUser.Registration = reg

    request := certificate.ObtainRequest{
        Domains: []string{domain},
        Bundle:  true,
    }
    certificates, err := client.Certificate.Obtain(request)
    if err != nil {
        log.Println(err)
        return err
    }
    saveDir := GetNginxConfPath("ssl/" + domain)
    if _, err := os.Stat(saveDir); os.IsNotExist(err) {
        err = os.Mkdir(saveDir, 0755)
        if err != nil {
            log.Println("fail to create", saveDir)
            return err
        }
    }

    // Each certificate comes back with the cert bytes, the bytes of the client's
    // private key, and a certificate URL. SAVE THESE TO DISK.
    err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
        certificates.Certificate, 0644)
    if err != nil {
        log.Println(err)
        return err
    }
    err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
        certificates.PrivateKey, 0644)
    if err != nil {
        log.Println(err)
        return err
    }

    ReloadNginx()

    return nil
}
