package accountsetup

import (
	"crypto/sha256"
	"embed"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"slices"
	"strings"
)

const (
	UpstreamRepository = "https://github.com/tidbcloud/byoc-account-setup"
	UpstreamCommit     = "16210282152a0f2eef56821bb6e47c7986034b68"
	ScriptName         = "tidbcloud-byoc-setup.sh"
)

var expectedSHA256 = map[string]string{
	ScriptName:                            "9811fc0649d8692cce42a622008373585f23e0592826e370a6a158bdea8ebd92",
	"tidbcloud-byoc-setup-deploy.yaml":    "b1c6c869f4001ce8bcf16f2c2178fcf8e7584bc36eb5076f2a9a39ed7503a631",
	"tidbcloud-byoc-setup-dataplane.yaml": "d9c191f090d95c2becc797af7cee65ef90687d17cc9cf963adbf6f9e5ad4397f",
	"tidbcloud-byoc-setup-o11y.yaml":      "e25a399ab9cdd60597a1f1c6ba7c2c29826584b03b212766b2485e25f8eac396",
}

//go:embed assets/*
var assets embed.FS

type File struct {
	Name   string `json:"name"`
	SHA256 string `json:"sha256"`
	data   []byte
}

type Bundle struct {
	UpstreamRepository string `json:"upstream_repository"`
	UpstreamCommit     string `json:"upstream_commit"`
	Digest             string `json:"digest"`
	Files              []File `json:"files"`
}

func PinnedBundle() (*Bundle, error) {
	names := make([]string, 0, len(expectedSHA256))
	for name := range expectedSHA256 {
		names = append(names, name)
	}
	slices.Sort(names)
	bundle := &Bundle{
		UpstreamRepository: UpstreamRepository,
		UpstreamCommit:     UpstreamCommit,
		Files:              make([]File, 0, len(names)),
	}
	for _, name := range names {
		data, err := assets.ReadFile(filepath.ToSlash(filepath.Join("assets", name)))
		if err != nil {
			return nil, fmt.Errorf("read embedded BYOC account-setup file %s: %w", name, err)
		}
		sum := sha256.Sum256(data)
		actual := hex.EncodeToString(sum[:])
		if actual != expectedSHA256[name] {
			return nil, fmt.Errorf("embedded BYOC account-setup file %s has digest %s, want %s", name, actual, expectedSHA256[name])
		}
		bundle.Files = append(bundle.Files, File{Name: name, SHA256: actual, data: data})
	}
	bundle.Digest = digestBundle(bundle)
	return bundle, nil
}

func digestBundle(bundle *Bundle) string {
	hash := sha256.New()
	_, _ = hash.Write([]byte(bundle.UpstreamRepository))
	_, _ = hash.Write([]byte{0})
	_, _ = hash.Write([]byte(bundle.UpstreamCommit))
	_, _ = hash.Write([]byte{0})
	for _, file := range bundle.Files {
		_, _ = hash.Write([]byte(file.Name))
		_, _ = hash.Write([]byte{0})
		_, _ = hash.Write([]byte(file.SHA256))
		_, _ = hash.Write([]byte{0})
	}
	return "sha256:" + hex.EncodeToString(hash.Sum(nil))
}

func (b *Bundle) Materialize(dir string) error {
	if b == nil {
		return errors.New("BYOC account-setup bundle is required")
	}
	if strings.TrimSpace(dir) == "" {
		return errors.New("BYOC account-setup destination is required")
	}
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return fmt.Errorf("create BYOC account-setup destination: %w", err)
	}
	for _, file := range b.Files {
		if filepath.Base(file.Name) != file.Name {
			return fmt.Errorf("unsafe BYOC account-setup filename %q", file.Name)
		}
		mode := os.FileMode(0o600)
		if file.Name == ScriptName {
			mode = 0o700
		}
		path := filepath.Join(dir, file.Name)
		if err := os.WriteFile(path, file.data, mode); err != nil {
			return fmt.Errorf("write BYOC account-setup file %s: %w", file.Name, err)
		}
		if err := os.Chmod(path, mode); err != nil {
			return fmt.Errorf("secure BYOC account-setup file %s: %w", file.Name, err)
		}
	}
	return nil
}

func (b *Bundle) Read(name string) ([]byte, error) {
	if b == nil {
		return nil, errors.New("BYOC account-setup bundle is required")
	}
	for _, file := range b.Files {
		if file.Name == name {
			return append([]byte(nil), file.data...), nil
		}
	}
	return nil, fmt.Errorf("BYOC account-setup bundle does not contain %q", name)
}
