package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"github.com/netbirdio/netbird/client/internal/updater/reposign"
)

const (
	envArtifactPrivateKey = "NB_ARTIFACT_PRIV_KEY"
)

var (
	signArtifactPrivKeyFile  string
	signArtifactArtifactFile string

	verifyArtifactPubKeyFile    string
	verifyArtifactFile          string
	verifyArtifactSignatureFile string

	verifyArtifactKeyPubKeyFile     string
	verifyArtifactKeyRootPubKeyFile string
	verifyArtifactKeySignatureFile  string
	verifyArtifactKeyRevocationFile string
)

var signArtifactCmd = &cobra.Command{
	Use:   "sign-artifact",
	Short: "Sign an artifact using an artifact private key",
	Long: `Sign a software artifact (e.g., update bundle or binary) using the artifact's private key.
This command produces a detached signature that can be verified using the corresponding artifact public key.`,
	SilenceUsage: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := handleSignArtifact(cmd, signArtifactPrivKeyFile, signArtifactArtifactFile); err != nil {
			return fmt.Errorf("failed to sign artifact: %w", err)
		}
		return nil
	},
}

var verifyArtifactCmd = &cobra.Command{
	Use:          "verify-artifact",
	Short:        "Verify an artifact signature using an artifact public key",
	Long:         `Verify a software artifact signature using the artifact's public key.`,
	SilenceUsage: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := handleVerifyArtifact(cmd, verifyArtifactPubKeyFile, verifyArtifactFile, verifyArtifactSignatureFile); err != nil {
			return fmt.Errorf("failed to verify artifact: %w", err)
		}
		return nil
	},
}

var verifyArtifactKeyCmd = &cobra.Command{
	Use:   "verify-artifact-key",
	Short: "Verify an artifact public key was signed by a root key",
	Long: `Verify that an artifact public key (or bundle) was properly signed by a root key.
This validates the chain of trust from the root key to the artifact key.`,
	SilenceUsage: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := handleVerifyArtifactKey(cmd, verifyArtifactKeyPubKeyFile, verifyArtifactKeyRootPubKeyFile, verifyArtifactKeySignatureFile, verifyArtifactKeyRevocationFile); err != nil {
			return fmt.Errorf("failed to verify artifact key: %w", err)
		}
		return nil
	},
}

func init() {
	rootCmd.AddCommand(signArtifactCmd)
	rootCmd.AddCommand(verifyArtifactCmd)
	rootCmd.AddCommand(verifyArtifactKeyCmd)

	signArtifactCmd.Flags().StringVar(&signArtifactPrivKeyFile, "artifact-key-file", "", fmt.Sprintf("Path to the artifact private key file used for signing (or set %s env var)", envArtifactPrivateKey))
	signArtifactCmd.Flags().StringVar(&signArtifactArtifactFile, "artifact-file", "", "Path to the artifact to be signed")

	// artifact-file is required, but artifact-key-file can come from env var
	if err := signArtifactCmd.MarkFlagRequired("artifact-file"); err != nil {
		panic(fmt.Errorf("mark artifact-file as required: %w", err))
	}

	verifyArtifactCmd.Flags().StringVar(&verifyArtifactPubKeyFile, "artifact-public-key-file", "", "Path to the artifact public key file")
	verifyArtifactCmd.Flags().StringVar(&verifyArtifactFile, "artifact-file", "", "Path to the artifact to be verified")
	verifyArtifactCmd.Flags().StringVar(&verifyArtifactSignatureFile, "signature-file", "", "Path to the signature file")

	if err := verifyArtifactCmd.MarkFlagRequired("artifact-public-key-file"); err != nil {
		panic(fmt.Errorf("mark artifact-public-key-file as required: %w", err))
	}
	if err := verifyArtifactCmd.MarkFlagRequired("artifact-file"); err != nil {
		panic(fmt.Errorf("mark artifact-file as required: %w", err))
	}
	if err := verifyArtifactCmd.MarkFlagRequired("signature-file"); err != nil {
		panic(fmt.Errorf("mark signature-file as required: %w", err))
	}

	verifyArtifactKeyCmd.Flags().StringVar(&verifyArtifactKeyPubKeyFile, "artifact-key-file", "", "Path to the artifact public key file or bundle")
	verifyArtifactKeyCmd.Flags().StringVar(&verifyArtifactKeyRootPubKeyFile, "root-key-file", "", "Path to the root public key file or bundle")
	verifyArtifactKeyCmd.Flags().StringVar(&verifyArtifactKeySignatureFile, "signature-file", "", "Path to the signature file")
	verifyArtifactKeyCmd.Flags().StringVar(&verifyArtifactKeyRevocationFile, "revocation-file", "", "Path to the revocation list file (optional)")

	if err := verifyArtifactKeyCmd.MarkFlagRequired("artifact-key-file"); err != nil {
		panic(fmt.Errorf("mark artifact-key-file as required: %w", err))
	}
	if err := verifyArtifactKeyCmd.MarkFlagRequired("root-key-file"); err != nil {
		panic(fmt.Errorf("mark root-key-file as required: %w", err))
	}
	if err := verifyArtifactKeyCmd.MarkFlagRequired("signature-file"); err != nil {
		panic(fmt.Errorf("mark signature-file as required: %w", err))
	}
}

func handleSignArtifact(cmd *cobra.Command, privKeyFile, artifactFile string) error {
	cmd.Println("🖋️  Signing artifact...")

	// Load private key from env var or file
	var privKeyPEM []byte
	var err error

	if envKey := os.Getenv(envArtifactPrivateKey); envKey != "" {
		// Use key from environment variable
		privKeyPEM = []byte(envKey)
	} else if privKeyFile != "" {
		// Fall back to file
		privKeyPEM, err = os.ReadFile(privKeyFile)
		if err != nil {
			return fmt.Errorf("read private key file: %w", err)
		}
	} else {
		return fmt.Errorf("artifact private key must be provided via %s environment variable or --artifact-key-file flag", envArtifactPrivateKey)
	}

	privateKey, err := reposign.ParseArtifactKey(privKeyPEM)
	if err != nil {
		return fmt.Errorf("failed to parse artifact private key: %w", err)
	}

	artifactData, err := os.ReadFile(artifactFile)
	if err != nil {
		return fmt.Errorf("read artifact file: %w", err)
	}

	signature, err := reposign.SignData(privateKey, artifactData)
	if err != nil {
		return fmt.Errorf("sign artifact: %w", err)
	}

	sigFile := artifactFile + ".sig"
	if err := os.WriteFile(artifactFile+".sig", signature, 0o600); err != nil {
		return fmt.Errorf("write signature file (%s): %w", sigFile, err)
	}

	cmd.Printf("✅ Artifact signed successfully.\n")
	cmd.Printf("Signature file: %s\n", sigFile)
	return nil
}

func handleVerifyArtifact(cmd *cobra.Command, pubKeyFile, artifactFile, signatureFile string) error {
	cmd.Println("🔍 Verifying artifact...")

	// Read artifact public key
	pubKeyPEM, err := os.ReadFile(pubKeyFile)
	if err != nil {
		return fmt.Errorf("read public key file: %w", err)
	}

	publicKey, err := reposign.ParseArtifactPubKey(pubKeyPEM)
	if err != nil {
		return fmt.Errorf("failed to parse artifact public key: %w", err)
	}

	// Read artifact data
	artifactData, err := os.ReadFile(artifactFile)
	if err != nil {
		return fmt.Errorf("read artifact file: %w", err)
	}

	// Read signature
	sigBytes, err := os.ReadFile(signatureFile)
	if err != nil {
		return fmt.Errorf("read signature file: %w", err)
	}

	signature, err := reposign.ParseSignature(sigBytes)
	if err != nil {
		return fmt.Errorf("failed to parse signature: %w", err)
	}

	// Validate artifact
	if err := reposign.ValidateArtifact([]reposign.PublicKey{publicKey}, artifactData, *signature); err != nil {
		return fmt.Errorf("artifact verification failed: %w", err)
	}

	cmd.Println("✅ Artifact signature is valid")
	cmd.Printf("Artifact: %s\n", artifactFile)
	cmd.Printf("Signed by key: %s\n", signature.KeyID)
	cmd.Printf("Signature timestamp: %s\n", signature.Timestamp.Format("2006-01-02 15:04:05 MST"))
	return nil
}

func handleVerifyArtifactKey(cmd *cobra.Command, artifactKeyFile, rootKeyFile, signatureFile, revocationFile string) error {
	cmd.Println("🔍 Verifying artifact key...")

	// Read artifact key data
	artifactKeyData, err := os.ReadFile(artifactKeyFile)
	if err != nil {
		return fmt.Errorf("read artifact key file: %w", err)
	}

	// Read root public key(s)
	rootKeyData, err := os.ReadFile(rootKeyFile)
	if err != nil {
		return fmt.Errorf("read root key file: %w", err)
	}

	rootPublicKeys, err := parseRootPublicKeys(rootKeyData)
	if err != nil {
		return fmt.Errorf("failed to parse root public key(s): %w", err)
	}

	// Read signature
	sigBytes, err := os.ReadFile(signatureFile)
	if err != nil {
		return fmt.Errorf("read signature file: %w", err)
	}

	signature, err := reposign.ParseSignature(sigBytes)
	if err != nil {
		return fmt.Errorf("failed to parse signature: %w", err)
	}

	// Read optional revocation list
	var revocationList *reposign.RevocationList
	if revocationFile != "" {
		revData, err := os.ReadFile(revocationFile)
		if err != nil {
			return fmt.Errorf("read revocation file: %w", err)
		}

		revocationList, err = reposign.ParseRevocationList(revData)
		if err != nil {
			return fmt.Errorf("failed to parse revocation list: %w", err)
		}
	}

	// Validate artifact key(s)
	validKeys, err := reposign.ValidateArtifactKeys(rootPublicKeys, artifactKeyData, *signature, revocationList)
	if err != nil {
		return fmt.Errorf("artifact key verification failed: %w", err)
	}

	cmd.Println("✅ Artifact key(s) verified successfully")
	cmd.Printf("Signed by root key: %s\n", signature.KeyID)
	cmd.Printf("Signature timestamp: %s\n", signature.Timestamp.Format("2006-01-02 15:04:05 MST"))
	cmd.Printf("\nValid artifact keys (%d):\n", len(validKeys))
	for i, key := range validKeys {
		cmd.Printf("  [%d] Key ID: %s\n", i+1, key.Metadata.ID)
		cmd.Printf("      Created: %s\n", key.Metadata.CreatedAt.Format("2006-01-02 15:04:05 MST"))
		if !key.Metadata.ExpiresAt.IsZero() {
			cmd.Printf("      Expires: %s\n", key.Metadata.ExpiresAt.Format("2006-01-02 15:04:05 MST"))
		} else {
			cmd.Printf("      Expires: Never\n")
		}
	}
	return nil
}

// parseRootPublicKeys parses a root public key from PEM data
func parseRootPublicKeys(data []byte) ([]reposign.PublicKey, error) {
	key, err := reposign.ParseRootPublicKey(data)
	if err != nil {
		return nil, err
	}
	return []reposign.PublicKey{key}, nil
}
