//go:build integration

package integration

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/hex"
	"fmt"
	"net"
	"os"

	challTestSrvClient "github.com/letsencrypt/boulder/test/chall-test-srv-client"

	"github.com/eggsampler/acme/v3"
)

var testSrvClient = challTestSrvClient.NewClient("")

func init() {
	// Go tests get run in the directory their source code lives in. For these
	// test cases, that would be "test/integration." However, it's easier to
	// reference test data and config files for integration tests relative to the
	// root of the Boulder repo, so we run all of these tests from there instead.
	os.Chdir("../../")
}

var (
	OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
)

func random_domain() string {
	var bytes [3]byte
	rand.Read(bytes[:])
	return hex.EncodeToString(bytes[:]) + ".com"
}

type client struct {
	acme.Account
	acme.Client
}

func makeClient(contacts ...string) (*client, error) {
	c, err := acme.NewClient("http://boulder.service.consul:4001/directory")
	if err != nil {
		return nil, fmt.Errorf("Error connecting to acme directory: %v", err)
	}
	privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("error creating private key: %v", err)
	}
	account, err := c.NewAccount(privKey, false, true, contacts...)
	if err != nil {
		return nil, err
	}
	return &client{account, c}, nil
}

func makeClientAndOrder(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool, profile string, certToReplace *x509.Certificate) (*client, *acme.Order, error) {
	var err error
	if c == nil {
		c, err = makeClient()
		if err != nil {
			return nil, nil, err
		}
	}

	var ids []acme.Identifier
	for _, ident := range idents {
		ids = append(ids, acme.Identifier{Type: string(ident.Type), Value: ident.Value})
	}
	var order acme.Order
	if certToReplace != nil {
		order, err = c.Client.ReplacementOrderExtension(c.Account, certToReplace, ids, acme.OrderExtension{Profile: profile})
	} else {
		order, err = c.Client.NewOrderExtension(c.Account, ids, acme.OrderExtension{Profile: profile})
	}
	if err != nil {
		return nil, nil, err
	}

	for _, authUrl := range order.Authorizations {
		auth, err := c.Client.FetchAuthorization(c.Account, authUrl)
		if err != nil {
			return nil, nil, fmt.Errorf("fetching authorization at %s: %s", authUrl, err)
		}

		chal, ok := auth.ChallengeMap[acme.ChallengeTypeHTTP01]
		if !ok {
			return nil, nil, fmt.Errorf("no HTTP challenge at %s", authUrl)
		}

		_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
		if err != nil {
			return nil, nil, err
		}
		chal, err = c.Client.UpdateChallenge(c.Account, chal)
		if err != nil {
			_, err = testSrvClient.RemoveHTTP01Response(chal.Token)
			if err != nil {
				return nil, nil, err
			}
			return nil, nil, err
		}
		_, err = testSrvClient.RemoveHTTP01Response(chal.Token)
		if err != nil {
			return nil, nil, err
		}
	}

	csr, err := makeCSR(csrKey, idents, cn)
	if err != nil {
		return nil, nil, err
	}

	order, err = c.Client.FinalizeOrder(c.Account, order, csr)
	if err != nil {
		return nil, nil, fmt.Errorf("finalizing order: %s", err)
	}

	return c, &order, nil
}

type issuanceResult struct {
	acme.Order
	certs []*x509.Certificate
}

func authAndIssue(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool, profile string) (*issuanceResult, error) {
	var err error

	c, order, err := makeClientAndOrder(c, csrKey, idents, cn, profile, nil)
	if err != nil {
		return nil, err
	}

	certs, err := c.Client.FetchCertificates(c.Account, order.Certificate)
	if err != nil {
		return nil, fmt.Errorf("fetching certificates: %s", err)
	}
	return &issuanceResult{*order, certs}, nil
}

type issuanceResultAllChains struct {
	acme.Order
	certs map[string][]*x509.Certificate
}

func authAndIssueFetchAllChains(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool) (*issuanceResultAllChains, error) {
	c, order, err := makeClientAndOrder(c, csrKey, idents, cn, "", nil)
	if err != nil {
		return nil, err
	}

	// Retrieve all the certificate chains served by the WFE2.
	certs, err := c.Client.FetchAllCertificates(c.Account, order.Certificate)
	if err != nil {
		return nil, fmt.Errorf("fetching certificates: %s", err)
	}

	return &issuanceResultAllChains{*order, certs}, nil
}

func makeCSR(k *ecdsa.PrivateKey, idents []acme.Identifier, cn bool) (*x509.CertificateRequest, error) {
	var err error
	if k == nil {
		k, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
		if err != nil {
			return nil, fmt.Errorf("generating certificate key: %s", err)
		}
	}

	var names []string
	var ips []net.IP
	for _, ident := range idents {
		switch ident.Type {
		case "dns":
			names = append(names, ident.Value)
		case "ip":
			ips = append(ips, net.ParseIP(ident.Value))
		default:
			return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
		}
	}

	tmpl := &x509.CertificateRequest{
		SignatureAlgorithm: x509.ECDSAWithSHA256,
		PublicKeyAlgorithm: x509.ECDSA,
		PublicKey:          k.Public(),
		DNSNames:           names,
		IPAddresses:        ips,
	}
	if cn {
		tmpl.Subject = pkix.Name{CommonName: names[0]}
	}

	csrDer, err := x509.CreateCertificateRequest(rand.Reader, tmpl, k)
	if err != nil {
		return nil, fmt.Errorf("making csr: %s", err)
	}
	csr, err := x509.ParseCertificateRequest(csrDer)
	if err != nil {
		return nil, fmt.Errorf("parsing csr: %s", err)
	}
	return csr, nil
}
