package libwallet

import (
	"encoding/json"
	"fmt"

	"github.com/muun/libwallet/emergencykit"
)

// EKInput input struct to fill the PDF
type EKInput struct {
	FirstEncryptedKey  string
	FirstFingerprint   string
	SecondEncryptedKey string
	SecondFingerprint  string
}

// EKOutput with the html as string and the verification code
type EKOutput struct {
	HTML             string
	VerificationCode string
	Metadata         string
}

// GenerateEmergencyKitHTML returns the translated html as a string along with the verification
// code and the kit metadata, represented in an opaque string.
// After calling this method, clients should use their Chromium/WebKit implementations to render
// the HTML into a PDF (better done there), and then come back to call `AddEmergencyKitMetadata`
// and produce the final PDF (better done here).
func GenerateEmergencyKitHTML(ekParams *EKInput, language string) (*EKOutput, error) {
	moduleInput := &emergencykit.Input{
		FirstEncryptedKey:  ekParams.FirstEncryptedKey,
		FirstFingerprint:   ekParams.FirstFingerprint,
		SecondEncryptedKey: ekParams.SecondEncryptedKey,
		SecondFingerprint:  ekParams.SecondFingerprint,
	}

	// Create the HTML and the verification code:
	htmlWithCode, err := emergencykit.GenerateHTML(moduleInput, language)
	if err != nil {
		return nil, fmt.Errorf("GenerateEkHtml failed to render: %w", err)
	}

	// Create and serialize the metadata:
	metadata, err := createEmergencyKitMetadata(ekParams)
	if err != nil {
		return nil, fmt.Errorf("GenerateEkHtml failed to create metadata: %w", err)
	}

	metadataBytes, err := json.Marshal(&metadata)
	if err != nil {
		return nil, fmt.Errorf("GenerateEkHtml failed to marshal %s: %w", string(metadataBytes), err)
	}

	output := &EKOutput{
		HTML:             htmlWithCode.HTML,
		VerificationCode: htmlWithCode.VerificationCode,
		Metadata:         string(metadataBytes),
	}

	return output, nil
}

// AddEmergencyKitMetadata produces a copy of the PDF file at `srcFile` with embedded metadata,
// writing it into `dstFile`. The provided metadata must be the same opaque string produced by
// `GenerateEmergencyKitHTML`.
func AddEmergencyKitMetadata(metadataText string, srcFile string, dstFile string) error {
	// Initialize the MetadataWriter:
	metadataWriter := &emergencykit.MetadataWriter{
		SrcFile: srcFile,
		DstFile: dstFile,
	}

	// Deserialize the metadata:
	var metadata emergencykit.Metadata

	err := json.Unmarshal([]byte(metadataText), &metadata)
	if err != nil {
		return fmt.Errorf("AddEkMetadata failed to unmarshal: %w", err)
	}

	err = metadataWriter.WriteMetadata(&metadata)
	if err != nil {
		return fmt.Errorf("AddEkMetadata failed to write metadata: %w", err)
	}

	return nil
}

func createEmergencyKitMetadata(ekParams *EKInput) (*emergencykit.Metadata, error) {
	// NOTE:
	// This method would be more naturally placed in the `emergencykit` module, but given the current
	// project structure (heavily determined by `gomobile` and the need for top-level bindings) and
	// the use of `decodeEncryptedPrivateKey` this isn't possible. Instead, we peek through the layer
	// boundary to craft the object here.

	// Decode both keys, to extract their inner properties:
	firstKey, err := DecodeEncryptedPrivateKey(ekParams.FirstEncryptedKey)
	if err != nil {
		return nil, fmt.Errorf("createEkMetadata failed to decode first key: %w", err)
	}

	secondKey, err := DecodeEncryptedPrivateKey(ekParams.SecondEncryptedKey)
	if err != nil {
		return nil, fmt.Errorf("createEkMetadata failed to decode second key: %w", err)
	}

	// Obtain the list of checksumed output descriptors:
	descriptors := emergencykit.GetDescriptors(&emergencykit.DescriptorsData{
		FirstFingerprint:  ekParams.FirstFingerprint,
		SecondFingerprint: ekParams.SecondFingerprint,
	})

	// Create the keys for the key array:
	keys := []*emergencykit.MetadataKey{
		createEmergencyKitMetadataKey(firstKey),
		createEmergencyKitMetadataKey(secondKey),
	}

	metadata := &emergencykit.Metadata{
		Version:           2,
		BirthdayBlock:     int(secondKey.Birthday),
		EncryptedKeys:     keys,
		OutputDescriptors: descriptors,
	}

	return metadata, nil
}

func createEmergencyKitMetadataKey(key *EncryptedPrivateKeyInfo) *emergencykit.MetadataKey {
	return &emergencykit.MetadataKey{
		DhPubKey:         key.EphPublicKey,
		EncryptedPrivKey: key.CipherText,
		Salt:             key.Salt,
	}
}
