// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package providercache

import (
	"archive/zip"
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/ProtonMail/go-crypto/openpgp/armor"
)

type providerPkg struct {
	zipContent    []byte
	shaSumContent []byte
	sigContent    []byte
	key           *bytes.Buffer
	keyID         string
	filePrefix    string // <provider_name>_<version>
}

// createTestProvider creates a test provider package with required verification files
func createTestProvider(t *testing.T, providerName, version string) *providerPkg {
	entity, pub := generateGPGKey(t)
	dir := t.TempDir()
	fileName := fmt.Sprintf("%s_%s", providerName, version)

	// Create a zip file for the provider
	zipFile := filepath.Join(dir, fileName)
	zipContent := createProviderZip(t, zipFile, fileName)

	// Generate SHA256SUMS file
	sumFile, sumContent, err := createSHASums(dir, fileName)
	if err != nil {
		t.Fatalf("failed to create SHA sums: %s", err)
	}

	// Sign the SHA256SUMS file
	sigContent, err := createDetachedSignature(sumFile, entity)
	if err != nil {
		t.Fatalf("failed to create signature: %s", err)
	}

	return &providerPkg{
		zipContent:    zipContent,
		shaSumContent: sumContent,
		sigContent:    sigContent,
		key:           pub,
		keyID:         entity.PrimaryKey.KeyIdString(),
		filePrefix:    fileName,
	}
}

func generateGPGKey(t *testing.T) (*openpgp.Entity, *bytes.Buffer) {
	entity, err := openpgp.NewEntity("Terraform Test", "test", "terraform@example.com", nil)
	if err != nil {
		t.Fatalf("failed to create entity: %s", err)
	}

	// Export the public key in armored format
	pubBuf := bytes.NewBuffer(nil)
	w, err := armor.Encode(pubBuf, openpgp.PublicKeyType, nil)
	if err != nil {
		t.Fatalf("failed to create armor writer: %s", err)
	}

	if err := entity.Serialize(w); err != nil {
		t.Fatalf("failed to serialize key: %s", err)
	}

	if err := w.Close(); err != nil {
		t.Fatalf("failed to finalize armor: %s", err)
	}

	return entity, pubBuf
}

func createProviderZip(t *testing.T, zipPath, filename string) []byte {
	zipBuffer := new(bytes.Buffer)
	zipWriter := zip.NewWriter(zipBuffer)

	// Add file to the archive
	_, err := zipWriter.Create(fmt.Sprintf("terraform-provider-%s", filename))
	if err != nil {
		t.Fatalf("failed to create zip entry: %s", err)
	}

	if err := zipWriter.Close(); err != nil {
		t.Fatalf("failed to close zip writer: %s", err)
	}

	// Write the zip to a file
	zipContent := zipBuffer.Bytes()
	if err := os.WriteFile(zipPath+".zip", zipContent, 0666); err != nil {
		t.Fatalf("failed to write zip file: %s", err)
	}

	return zipContent
}

// createDetachedSignature creates a PGP detached signature for the given file
func createDetachedSignature(filePath string, entity *openpgp.Entity) ([]byte, error) {
	r, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer safeClose(r)

	sigPath := filePath + ".sig"
	w, err := os.Create(sigPath)
	if err != nil {
		return nil, err
	}
	defer safeClose(w)

	if err := openpgp.DetachSign(w, entity, r, nil); err != nil {
		return nil, err
	}

	// Read the signature back
	return os.ReadFile(sigPath)
}

// createSHASums creates SHA256 sums for all files in the directory
func createSHASums(dir, name string) (string, []byte, error) {
	var sums []string

	files, err := os.ReadDir(dir)
	if err != nil {
		return "", nil, err
	}

	for _, file := range files {
		if file.IsDir() {
			continue
		}

		filePath := filepath.Join(dir, file.Name())
		sum, err := calculateFileHash(filePath)
		if err != nil {
			return "", nil, err
		}

		sums = append(sums, fmt.Sprintf("%x  %s", sum, file.Name()))
	}

	sumContent := []byte(strings.Join(sums, "\n") + "\n")
	sumPath := filepath.Join(dir, fmt.Sprintf("%s_SHA256SUMS", name))

	if err := os.WriteFile(sumPath, sumContent, 0666); err != nil {
		return "", nil, err
	}

	return sumPath, sumContent, nil
}

// calculateFileHash returns the SHA256 hash of a file
func calculateFileHash(path string) ([]byte, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer safeClose(f)

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return nil, err
	}

	return h.Sum(nil), nil
}

// safeClose safely closes a file, logging any errors
func safeClose(f *os.File) {
	if err := f.Close(); err != nil {
		fmt.Printf("failed to close file: %s\n", err)
	}
}
