package cmds

import (
	"context"
	"crypto/tls"
	"fmt"
	"log/slog"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/spf13/cobra"

	"github.com/pocket-id/pocket-id/backend/internal/common"
)

type healthcheckFlags struct {
	Endpoint   string
	UnixSocket string
	Verbose    bool
}

type healthcheckResult struct {
	StatusCode int
	URL        string
}

func init() {
	var flags healthcheckFlags

	healthcheckCmd := &cobra.Command{
		Use:   "healthcheck",
		Short: "Performs a healthcheck of a running Pocket ID instance",
		Run: func(cmd *cobra.Command, args []string) {
			start := time.Now()

			ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
			defer cancel()

			if flags.UnixSocket == "" && !cmd.Flags().Changed("endpoint") {
				flags.UnixSocket = common.EnvConfig.UnixSocket
			}

			result, err := healthcheck(ctx, flags)
			if err != nil {
				slog.ErrorContext(ctx,
					"Healthcheck failed",
					"error", err,
					"ms", time.Since(start).Milliseconds(),
				)
				os.Exit(1)
			}

			if flags.Verbose {
				slog.InfoContext(ctx,
					"Healthcheck succeeded",
					"status", result.StatusCode,
					"url", result.URL,
					"unixSocket", flags.UnixSocket,
					"ms", time.Since(start).Milliseconds(),
				)
			}
		},
	}

	healthcheckCmd.Flags().StringVarP(&flags.Endpoint, "endpoint", "e", defaultEndpoint(), "Endpoint for Pocket ID")
	healthcheckCmd.Flags().StringVar(&flags.UnixSocket, "unix-socket", "", "UNIX socket path for Pocket ID")
	healthcheckCmd.Flags().BoolVarP(&flags.Verbose, "verbose", "v", false, "Enable verbose mode")

	rootCmd.AddCommand(healthcheckCmd)
}

// The server serves TLS when either inline certificate data or certificate files are configured
func tlsEnabled() bool {
	return (common.EnvConfig.TLSCert != "" && common.EnvConfig.TLSKey != "") ||
		(common.EnvConfig.TLSCertFile != "" && common.EnvConfig.TLSKeyFile != "")
}

func defaultEndpoint() string {
	scheme := "http"
	if tlsEnabled() {
		scheme = "https"
	}

	return scheme + "://localhost:" + common.EnvConfig.Port
}

func healthcheck(ctx context.Context, flags healthcheckFlags) (*healthcheckResult, error) {
	url := strings.TrimRight(flags.Endpoint, "/") + "/healthz"
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request object for %q: %w", url, err)
	}

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

	if flags.UnixSocket != "" {
		transport.Proxy = nil
		transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
			dialer := net.Dialer{}
			return dialer.DialContext(ctx, "unix", flags.UnixSocket)
		}
	}

	// If TLS cert and key files are provided, there is a high chance that the server is using a self-signed certificate
	if tlsEnabled() {
		transport.TLSClientConfig = &tls.Config{
			InsecureSkipVerify: true, //nolint:gosec // no sensitive data is transmitted in healthcheck
		}
	}

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

	res, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to perform request to %q: %w", url, err)
	}
	defer res.Body.Close()

	if res.StatusCode < 200 || res.StatusCode >= 300 {
		return nil, fmt.Errorf("unexpected status %d from %q", res.StatusCode, url)
	}

	return &healthcheckResult{
		StatusCode: res.StatusCode,
		URL:        url,
	}, nil
}
