package app

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	securejoin "github.com/cyphar/filepath-securejoin"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"

	"gitlab.com/gitlab-org/release-cli/internal/flags"
	"gitlab.com/gitlab-org/release-cli/internal/gitlab"
)

func newHTTPClient(ctx *cli.Context, log logrus.FieldLogger) (gitlab.HTTPClient, error) {
	extraCA := ctx.String(flags.AdditionalCACertBundle)

	rootCAs, err := x509.SystemCertPool()
	if err != nil {
		// SystemCertPool is not supported on Windows, safe to call x509.NewCertPool() below
		if runtime.GOOS != "windows" {
			return nil, fmt.Errorf("getting system cert pool: %w", err)
		}

		rootCAs = x509.NewCertPool()
	}

	if extraCA != "" {
		if err := loadExtraCACert(rootCAs, extraCA, log); err != nil {
			return nil, err
		}
	}

	transport := http.DefaultTransport.(*http.Transport).Clone()

	transport.TLSClientConfig = &tls.Config{
		// nolint: gosec
		// G402: TLS InsecureSkipVerify may be true
		// set to true at your own risk
		InsecureSkipVerify: ctx.Bool(flags.InsecureHTTPS),
		RootCAs:            rootCAs,
	}

	return &http.Client{
		Timeout:   ctx.Duration(flags.Timeout),
		Transport: transport,
	}, nil
}

func loadExtraCACert(rootCAs *x509.CertPool, ca string, log logrus.FieldLogger) error {
	l := log.WithFields(logrus.Fields{
		flags.AdditionalCACertBundle: ca,
	})

	caBytes, err := getCA(ca)
	if err != nil {
		return err
	}

	// Append our cert to the system pool
	if ok := rootCAs.AppendCertsFromPEM(caBytes); !ok {
		l.Warn("No certs appended, using system certs only")
	}

	return nil
}

func getCA(ca string) ([]byte, error) {
	// If a new line is found it means that we were given a CA block rather than a path.
	if strings.ContainsRune(ca, '\n') {
		return []byte(ca), nil
	}

	baseDir, err := os.Getwd()
	if err != nil {
		return nil, err
	}

	filePath, err := securejoin.SecureJoin(baseDir, ca)
	if err != nil {
		return nil, err
	}

	if _, statErr := os.Stat(filePath); errors.Is(statErr, os.ErrNotExist) {
		buildsDir := os.Getenv("CI_BUILDS_DIR")
		if buildsDir == "" {
			return nil, statErr
		}

		rel, err := filepath.Rel(buildsDir, ca)
		if err != nil {
			return nil, err
		}

		filePath, err = securejoin.SecureJoin(buildsDir, rel)
		if err != nil {
			return nil, err
		}
	}

	return os.ReadFile(filepath.Clean(filePath))
}
