package main

import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"net/http"
	"os"

	"github.com/spf13/cobra"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"

	"git.wntrmute.dev/mc/mcdsl/terminal"
	metacryptv1 "git.wntrmute.dev/mc/metacrypt/gen/metacrypt/v1"
)

var unsealCmd = &cobra.Command{
	Use:   "unseal",
	Short: "Unseal the Metacrypt service",
	Long:  "Send an unseal request to a running Metacrypt server over TLS. Uses gRPC when --grpc-addr is provided, otherwise falls back to the REST API.",
	RunE:  runUnseal,
}

var (
	unsealAddr     string
	unsealGRPCAddr string
	unsealCACert   string
)

func init() {
	unsealCmd.Flags().StringVar(&unsealAddr, "addr", "", "REST server address (e.g., https://localhost:8443)")
	unsealCmd.Flags().StringVar(&unsealGRPCAddr, "grpc-addr", "", "gRPC server address (e.g., localhost:9443); preferred over --addr when set")
	unsealCmd.Flags().StringVar(&unsealCACert, "ca-cert", "", "path to CA certificate for TLS verification")
	rootCmd.AddCommand(unsealCmd)
}

func runUnseal(cmd *cobra.Command, args []string) error {
	if unsealGRPCAddr == "" && unsealAddr == "" {
		return fmt.Errorf("one of --grpc-addr or --addr is required")
	}

	password, err := terminal.ReadPassword("Unseal password: ")
	if err != nil {
		return fmt.Errorf("read password: %w", err)
	}

	if unsealGRPCAddr != "" {
		return unsealViaGRPC(unsealGRPCAddr, unsealCACert, password)
	}
	return unsealViaREST(unsealAddr, unsealCACert, password)
}

func buildTLSConfig(caCertPath string) (*tls.Config, error) {
	tlsCfg := &tls.Config{MinVersion: tls.VersionTLS13}
	if caCertPath != "" {
		pem, err := os.ReadFile(caCertPath) //nolint:gosec
		if err != nil {
			return nil, fmt.Errorf("read CA cert: %w", err)
		}
		pool := x509.NewCertPool()
		if !pool.AppendCertsFromPEM(pem) {
			return nil, fmt.Errorf("no valid certs in CA file")
		}
		tlsCfg.RootCAs = pool
	}
	return tlsCfg, nil
}

func unsealViaGRPC(addr, caCertPath, password string) error {
	tlsCfg, err := buildTLSConfig(caCertPath)
	if err != nil {
		return err
	}

	conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)))
	if err != nil {
		return fmt.Errorf("grpc dial: %w", err)
	}
	defer func() { _ = conn.Close() }()

	client := metacryptv1.NewSystemServiceClient(conn)
	resp, err := client.Unseal(context.Background(), &metacryptv1.UnsealRequest{Password: password})
	if err != nil {
		return fmt.Errorf("unseal failed: %w", err)
	}

	fmt.Printf("State: %s\n", resp.State)
	return nil
}

func unsealViaREST(addr, caCertPath, password string) error {
	tlsCfg, err := buildTLSConfig(caCertPath)
	if err != nil {
		return err
	}

	client := &http.Client{
		Transport: &http.Transport{TLSClientConfig: tlsCfg},
	}

	body, err := json.Marshal(map[string]string{"password": password})
	if err != nil {
		return fmt.Errorf("encode request: %w", err)
	}

	resp, err := client.Post(addr+"/v1/unseal", "application/json", bytes.NewReader(body))
	if err != nil {
		return err
	}
	defer func() { _ = resp.Body.Close() }()

	var result struct {
		State string `json:"state"`
		Error string `json:"error"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return fmt.Errorf("decode response: %w", err)
	}

	if result.Error != "" {
		return fmt.Errorf("unseal failed: %s", result.Error)
	}

	fmt.Printf("State: %s\n", result.State)
	return nil
}
