package main

import (
	"bytes"
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime/debug"
	"strings"
	"time"

	"github.com/pingcap-inc/nexuscrm-controlplane/releaseartifact"
)

type publisherPublicKey struct {
	SchemaVersion string `json:"schema_version"`
	PublisherID   string `json:"publisher_id"`
	KeyID         string `json:"key_id"`
	Algorithm     string `json:"algorithm"`
	PublicKey     []byte `json:"public_key"`
}

var exactSourceRevision = regexp.MustCompile(`^[0-9a-f]{40}$`)

const contractModulePath = "github.com/pingcap-inc/nexuscrm-contract"

func main() {
	if err := run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func run(args []string) error {
	if len(args) == 0 {
		return errors.New("usage: crm-app-release <build|attest-one-shot|verify>")
	}
	switch args[0] {
	case "build":
		flags := flag.NewFlagSet("build", flag.ContinueOnError)
		packRoot := flags.String("pack-root", "", "salesforce-crm-standard directory")
		output := flags.String("output", "", "canonical AppPackage artifact output")
		if err := flags.Parse(args[1:]); err != nil {
			return err
		}
		if *packRoot == "" || *output == "" {
			return errors.New("build requires --pack-root and --output")
		}
		verified, raw, _, err := buildCRMRelease(*packRoot)
		if err != nil {
			return err
		}
		if err := os.MkdirAll(filepath.Dir(*output), 0o755); err != nil {
			return err
		}
		if err := os.WriteFile(*output, raw, 0o644); err != nil {
			return err
		}
		fmt.Println(verified.AppReleaseDigest)
		return nil
	case "attest-one-shot":
		flags := flag.NewFlagSet("attest-one-shot", flag.ContinueOnError)
		packRoot := flags.String("pack-root", "", "salesforce-crm-standard directory")
		packagePath := flags.String("package", "", "canonical AppPackage artifact")
		attestationPath := flags.String("attestation", "", "attestation output")
		publicKeyPath := flags.String("public-key", "", "one-release verification key output")
		keyID := flags.String("key-id", "", "publisher key identifier")
		sourceRevision := flags.String("source-revision", "", "immutable source revision")
		issuedAtRaw := flags.String("issued-at", "", "canonical UTC RFC3339 timestamp")
		if err := flags.Parse(args[1:]); err != nil {
			return err
		}
		if *packRoot == "" || *packagePath == "" || *attestationPath == "" || *publicKeyPath == "" || *keyID == "" || *sourceRevision == "" || *issuedAtRaw == "" {
			return errors.New("attest-one-shot requires --pack-root, --package, --attestation, --public-key, --key-id, --source-revision, and --issued-at")
		}
		if !exactSourceRevision.MatchString(*sourceRevision) {
			return errors.New("attest-one-shot --source-revision must be a full lowercase Git commit SHA")
		}
		issuedAt, err := time.Parse(time.RFC3339Nano, *issuedAtRaw)
		if err != nil {
			return err
		}
		if issuedAt.Location() != time.UTC || issuedAt.Format(time.RFC3339Nano) != *issuedAtRaw {
			return errors.New("attest-one-shot --issued-at must be canonical UTC RFC3339")
		}
		if issuedAt.After(time.Now().UTC()) {
			return errors.New("attest-one-shot --issued-at must not be in the future")
		}
		exactPackRoot, cleanup, err := materializeExactSourceCheckout(*packRoot, *sourceRevision)
		if err != nil {
			return err
		}
		defer cleanup()
		verified, expectedRaw, sourceDigest, err := buildCRMRelease(exactPackRoot)
		if err != nil {
			return err
		}
		actualRaw, err := os.ReadFile(*packagePath)
		if err != nil {
			return err
		}
		if string(actualRaw) != string(expectedRaw) {
			return errors.New("checked AppPackage does not match the exact current source inputs")
		}
		publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
		if err != nil {
			return err
		}
		contractModuleVersion, err := dependencyModuleVersion(contractModulePath)
		if err != nil {
			return err
		}
		provenance := releaseartifact.Provenance{SchemaVersion: releaseartifact.ProvenanceSchemaVersion,
			SourceRepository: "pingcap-inc/nexuscrm-app-packs", SourceRevision: *sourceRevision,
			SourcePath: crmPackName, SourceTreeDigest: sourceDigest,
			BuilderID: "github.com/pingcap-inc/nexuscrm-app-packs/tools/crm-app-release/v1", ContractModuleVersion: contractModuleVersion}
		_, attestationRaw, err := releaseartifact.SignAttestation(verified, provenance, *keyID, issuedAt, privateKey)
		if err != nil {
			return err
		}
		keyRaw, err := json.Marshal(publisherPublicKey{SchemaVersion: "nexuscrm.publisher-public-key/v1", PublisherID: crmPublisherID, KeyID: *keyID, Algorithm: releaseartifact.SignatureAlgorithm, PublicKey: publicKey})
		if err != nil {
			return err
		}
		if err := os.WriteFile(*attestationPath, attestationRaw, 0o644); err != nil {
			return err
		}
		return os.WriteFile(*publicKeyPath, keyRaw, 0o644)
	case "verify":
		flags := flag.NewFlagSet("verify", flag.ContinueOnError)
		packagePath := flags.String("package", "", "canonical AppPackage artifact")
		attestationPath := flags.String("attestation", "", "publisher attestation")
		publicKeyPath := flags.String("public-key", "", "publisher public key document")
		if err := flags.Parse(args[1:]); err != nil {
			return err
		}
		packageRaw, err := os.ReadFile(*packagePath)
		if err != nil {
			return err
		}
		attestationRaw, err := os.ReadFile(*attestationPath)
		if err != nil {
			return err
		}
		keyRaw, err := os.ReadFile(*publicKeyPath)
		if err != nil {
			return err
		}
		var key publisherPublicKey
		if err := json.Unmarshal(keyRaw, &key); err != nil {
			return err
		}
		canonicalKey, err := json.Marshal(key)
		if err != nil {
			return err
		}
		if string(keyRaw) != string(canonicalKey) || key.SchemaVersion != "nexuscrm.publisher-public-key/v1" || key.PublisherID != crmPublisherID || key.Algorithm != releaseartifact.SignatureAlgorithm || len(key.PublicKey) != ed25519.PublicKeySize {
			return errors.New("publisher public key document is invalid or non-canonical")
		}
		verified, err := releaseartifact.Verify(packageRaw, attestationRaw, ed25519.PublicKey(key.PublicKey))
		if err != nil {
			return err
		}
		if verified.Attestation.KeyID != key.KeyID {
			return errors.New("publisher key document does not match attestation key_id")
		}
		fmt.Printf("%s %s\n", verified.Package.AppReleaseDigest, base64.StdEncoding.EncodeToString(key.PublicKey))
		return nil
	default:
		return fmt.Errorf("unknown command %q", args[0])
	}
}

func dependencyModuleVersion(modulePath string) (string, error) {
	buildInfo, ok := debug.ReadBuildInfo()
	if !ok {
		return "", errors.New("release builder has no Go build information")
	}
	return dependencyModuleVersionFromBuildInfo(buildInfo, modulePath)
}

func dependencyModuleVersionFromBuildInfo(buildInfo *debug.BuildInfo, modulePath string) (string, error) {
	if buildInfo == nil || modulePath == "" {
		return "", errors.New("release builder dependency lookup is invalid")
	}
	for _, dependency := range buildInfo.Deps {
		if dependency == nil || dependency.Path != modulePath {
			continue
		}
		resolved := dependency
		if dependency.Replace != nil {
			if dependency.Replace.Path != modulePath {
				return "", fmt.Errorf("release builder dependency %s is replaced by non-authoritative module %s", modulePath, dependency.Replace.Path)
			}
			resolved = dependency.Replace
		}
		if resolved.Version == "" || resolved.Version == "(devel)" || !strings.HasPrefix(resolved.Version, "v") {
			return "", fmt.Errorf("release builder dependency %s is not pinned to a published module version", modulePath)
		}
		return resolved.Version, nil
	}
	return "", fmt.Errorf("release builder dependency %s is absent from Go build information", modulePath)
}

func verifySourceCheckout(packRoot, sourceRevision string) error {
	_, cleanup, err := materializeExactSourceCheckout(packRoot, sourceRevision)
	if cleanup != nil {
		cleanup()
	}
	return err
}

func materializeExactSourceCheckout(packRoot, sourceRevision string) (string, func(), error) {
	resolvedPackRoot, err := filepath.EvalSymlinks(packRoot)
	if err != nil {
		return "", nil, fmt.Errorf("resolve CRM pack root: %w", err)
	}
	resolvedPackRoot, err = filepath.Abs(resolvedPackRoot)
	if err != nil {
		return "", nil, fmt.Errorf("resolve absolute CRM pack root: %w", err)
	}
	repositoryRootRaw, err := gitOutput(resolvedPackRoot, "rev-parse", "--show-toplevel")
	if err != nil {
		return "", nil, errors.New("attest-one-shot --pack-root must be inside the exact Git source checkout")
	}
	repositoryRoot, err := filepath.EvalSymlinks(strings.TrimSpace(string(repositoryRootRaw)))
	if err != nil {
		return "", nil, fmt.Errorf("resolve Git source root: %w", err)
	}
	relativePackRoot, err := filepath.Rel(repositoryRoot, resolvedPackRoot)
	if err != nil || filepath.ToSlash(relativePackRoot) != crmPackName {
		return "", nil, fmt.Errorf("attest-one-shot --pack-root must be the repository %s directory", crmPackName)
	}
	headRaw, err := gitOutput(repositoryRoot, "rev-parse", "HEAD")
	if err != nil {
		return "", nil, err
	}
	if strings.TrimSpace(string(headRaw)) != sourceRevision {
		return "", nil, fmt.Errorf("attest-one-shot --source-revision %s does not match pack checkout HEAD %s", sourceRevision, strings.TrimSpace(string(headRaw)))
	}
	status, err := gitOutput(repositoryRoot, "status", "--porcelain=v1", "--untracked-files=all", "--", relativePackRoot)
	if err != nil {
		return "", nil, err
	}
	if len(bytes.TrimSpace(status)) != 0 {
		return "", nil, errors.New("attest-one-shot --pack-root differs from the declared source revision")
	}
	manifestRaw, err := gitOutput(repositoryRoot, "show", sourceRevision+":"+filepath.ToSlash(filepath.Join(relativePackRoot, "pack.json")))
	if err != nil {
		return "", nil, err
	}
	var manifest legacyPackManifest
	if err := json.Unmarshal(manifestRaw, &manifest); err != nil {
		return "", nil, fmt.Errorf("parse pack.json for source proof: %w", err)
	}
	exactRoot, err := os.MkdirTemp("", "nexuscrm-crm-release-source-")
	if err != nil {
		return "", nil, err
	}
	cleanup := func() { _ = os.RemoveAll(exactRoot) }
	if err := os.WriteFile(filepath.Join(exactRoot, "pack.json"), manifestRaw, 0o644); err != nil {
		cleanup()
		return "", nil, err
	}
	commandSources := runtimeFlowCommandSources(manifest.Version)
	sourcePaths := make([]string, 0, len(manifest.Components)+len(commandSources))
	for _, component := range manifest.Components {
		sourcePaths = append(sourcePaths, component.Path)
	}
	for _, source := range commandSources {
		sourcePaths = append(sourcePaths, source.path)
	}
	for index, sourcePath := range sourcePaths {
		cleanPath, err := canonicalSourcePath(sourcePath)
		if err != nil {
			cleanup()
			return "", nil, fmt.Errorf("sourcePaths[%d]: %w", index, err)
		}
		gitRaw, err := gitOutput(repositoryRoot, "show", sourceRevision+":"+filepath.ToSlash(filepath.Join(relativePackRoot, cleanPath)))
		if err != nil {
			cleanup()
			return "", nil, err
		}
		target := filepath.Join(exactRoot, filepath.FromSlash(cleanPath))
		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
			cleanup()
			return "", nil, err
		}
		if err := os.WriteFile(target, gitRaw, 0o644); err != nil {
			cleanup()
			return "", nil, err
		}
	}
	return exactRoot, cleanup, nil
}

func gitOutput(directory string, args ...string) ([]byte, error) {
	command := exec.Command("git", append([]string{"-C", directory}, args...)...)
	output, err := command.Output()
	if err != nil {
		return nil, fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
	}
	return output, nil
}
