//go:build wasip1

package main

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"enclave/internal/crypto/bip44"
	"enclave/internal/crypto/mpc"
	"enclave/internal/keybase"
	"enclave/internal/state"
	"enclave/internal/types"

	"github.com/extism/go-pdk"
	"github.com/sonr-io/crypto/core/protocol"
)

func main() { state.Default() }

//go:wasmexport ping
func ping() int32 {
	pdk.Log(pdk.LogInfo, "ping: received request")

	var input types.PingInput
	if err := pdk.InputJSON(&input); err != nil {
		output := types.PingOutput{
			Success: false,
			Message: fmt.Sprintf("failed to parse input: %s", err),
		}
		pdk.OutputJSON(output)
		return 0
	}

	output := types.PingOutput{
		Success: true,
		Message: "pong",
		Echo:    input.Message,
	}

	if err := pdk.OutputJSON(output); err != nil {
		pdk.Log(pdk.LogError, fmt.Sprintf("ping: failed to output: %s", err))
		return 1
	}

	pdk.Log(pdk.LogInfo, fmt.Sprintf("ping: responded with echo=%s", input.Message))
	return 0
}

//go:wasmexport generate
func generate() int32 {
	pdk.Log(pdk.LogInfo, "generate: starting database initialization with MPC")

	var input types.GenerateInput
	if err := pdk.InputJSON(&input); err != nil {
		pdk.SetError(fmt.Errorf("generate: failed to parse input: %w", err))
		return 1
	}

	if input.Credential == "" {
		pdk.SetError(errors.New("generate: credential is required"))
		return 1
	}

	credentialBytes, err := base64.StdEncoding.DecodeString(input.Credential)
	if err != nil {
		pdk.SetError(fmt.Errorf("generate: invalid base64 credential: %w", err))
		return 1
	}

	result, err := initializeWithMPC(credentialBytes)
	if err != nil {
		pdk.SetError(fmt.Errorf("generate: failed to initialize: %w", err))
		return 1
	}

	state.SetInitialized(true)
	state.SetDID(result.DID)

	dbBytes, err := serializeDatabase()
	if err != nil {
		pdk.SetError(fmt.Errorf("generate: failed to serialize database: %w", err))
		return 1
	}

	output := types.GenerateOutput{
		DID:       result.DID,
		Database:  dbBytes,
		EnclaveID: result.EnclaveID,
		PublicKey: result.PublicKey,
		Accounts:  result.Accounts,
	}

	if err := pdk.OutputJSON(output); err != nil {
		pdk.SetError(fmt.Errorf("generate: failed to output result: %w", err))
		return 1
	}

	pdk.Log(pdk.LogInfo, fmt.Sprintf("generate: created DID %s with enclave %s", result.DID, result.EnclaveID))
	return 0
}

//go:wasmexport load
func load() int32 {
	pdk.Log(pdk.LogInfo, "load: loading database from buffer")

	var input types.LoadInput
	if err := pdk.InputJSON(&input); err != nil {
		pdk.SetError(fmt.Errorf("load: failed to parse input: %w", err))
		return 1
	}

	if len(input.Database) == 0 {
		pdk.SetError(errors.New("load: database buffer is required"))
		return 1
	}

	did, err := loadDatabase(input.Database)
	if err != nil {
		output := types.LoadOutput{
			Success: false,
			Error:   err.Error(),
		}
		pdk.OutputJSON(output)
		return 1
	}

	state.SetInitialized(true)
	state.SetDID(did)

	output := types.LoadOutput{
		Success: true,
		DID:     did,
	}

	if err := pdk.OutputJSON(output); err != nil {
		pdk.SetError(fmt.Errorf("load: failed to output result: %w", err))
		return 1
	}

	pdk.Log(pdk.LogInfo, fmt.Sprintf("load: loaded database for DID %s", did))
	return 0
}

//go:wasmexport exec
func exec() int32 {
	pdk.Log(pdk.LogInfo, "exec: executing action")

	if !state.IsInitialized() {
		output := types.ExecOutput{Success: false, Error: "database not initialized, call generate or load first"}
		pdk.OutputJSON(output)
		return 0
	}

	var input types.ExecInput
	if err := pdk.InputJSON(&input); err != nil {
		output := types.ExecOutput{Success: false, Error: fmt.Sprintf("failed to parse input: %s", err)}
		pdk.OutputJSON(output)
		return 0
	}

	if input.Filter == "" {
		output := types.ExecOutput{Success: false, Error: "filter is required"}
		pdk.OutputJSON(output)
		return 0
	}

	params, err := parseFilter(input.Filter)
	if err != nil {
		output := types.ExecOutput{Success: false, Error: fmt.Sprintf("invalid filter: %s", err)}
		pdk.OutputJSON(output)
		return 0
	}

	if input.Token != "" {
		if err := validateUCAN(input.Token, params); err != nil {
			output := types.ExecOutput{
				Success: false,
				Error:   fmt.Sprintf("authorization failed: %s", err.Error()),
			}
			pdk.OutputJSON(output)
			return 1
		}
	}

	result, err := executeAction(params)
	if err != nil {
		output := types.ExecOutput{
			Success: false,
			Error:   err.Error(),
		}
		pdk.OutputJSON(output)
		return 1
	}

	output := types.ExecOutput{
		Success: true,
		Result:  result,
	}

	pdk.OutputJSON(output)
	pdk.Log(pdk.LogInfo, fmt.Sprintf("exec: completed %s on %s", params.Action, params.Resource))
	return 0
}

//go:wasmexport query
func query() int32 {
	pdk.Log(pdk.LogInfo, "query: resolving DID document")

	if !state.IsInitialized() {
		pdk.SetError(errors.New("database not initialized, call generate or load first"))
		return 1
	}

	var input types.QueryInput
	if err := pdk.InputJSON(&input); err != nil {
		pdk.SetError(fmt.Errorf("query: failed to parse input: %w", err))
		return 1
	}

	if input.DID == "" {
		input.DID = state.GetDID()
	}

	if !strings.HasPrefix(input.DID, "did:") {
		pdk.SetError(errors.New("query: invalid DID format"))
		return 1
	}

	output, err := resolveDID(input.DID)
	if err != nil {
		pdk.SetError(fmt.Errorf("query: failed to resolve DID: %w", err))
		return 1
	}

	if err := pdk.OutputJSON(output); err != nil {
		pdk.SetError(fmt.Errorf("query: failed to output result: %w", err))
		return 1
	}

	pdk.Log(pdk.LogInfo, fmt.Sprintf("query: resolved DID %s", input.DID))
	return 0
}

type initResult struct {
	DID       string
	EnclaveID string
	PublicKey string
	Accounts  []types.AccountInfo
}

func initializeWithMPC(credentialBytes []byte) (*initResult, error) {
	kb, err := keybase.Open()
	if err != nil {
		return nil, fmt.Errorf("open database: %w", err)
	}

	ctx := context.Background()
	did, err := kb.Initialize(ctx, credentialBytes)
	if err != nil {
		return nil, fmt.Errorf("initialize: %w", err)
	}

	pdk.Log(pdk.LogInfo, "initializeWithMPC: generating MPC enclave")
	enclave, err := mpc.NewEnclave()
	if err != nil {
		return nil, fmt.Errorf("generate MPC enclave: %w", err)
	}

	enclaveData := enclave.GetData()
	enclaveID := fmt.Sprintf("enc_%x", credentialBytes[:8])

	valShareStr, err := protocol.EncodeMessage(enclaveData.ValShare)
	if err != nil {
		return nil, fmt.Errorf("encode val share: %w", err)
	}
	userShareStr, err := protocol.EncodeMessage(enclaveData.UserShare)
	if err != nil {
		return nil, fmt.Errorf("encode user share: %w", err)
	}

	am, err := keybase.NewActionManager()
	if err != nil {
		return nil, fmt.Errorf("action manager: %w", err)
	}

	enc, err := am.CreateEnclave(ctx, keybase.NewEnclaveInput{
		EnclaveID:    enclaveID,
		PublicKeyHex: enclaveData.PubHex,
		PublicKey:    enclaveData.PubBytes,
		ValShare:     []byte(valShareStr),
		UserShare:    []byte(userShareStr),
		Nonce:        enclaveData.Nonce,
		Curve:        string(enclaveData.Curve),
	})
	if err != nil {
		return nil, fmt.Errorf("store enclave: %w", err)
	}

	pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeWithMPC: stored enclave %s", enclaveID))

	accounts, err := createDefaultAccounts(ctx, am, enc.ID, enclaveData.PubBytes)
	if err != nil {
		pdk.Log(pdk.LogWarn, fmt.Sprintf("initializeWithMPC: failed to create accounts: %s", err))
		accounts = []types.AccountInfo{}
	}

	return &initResult{
		DID:       did,
		EnclaveID: enclaveID,
		PublicKey: enclaveData.PubHex,
		Accounts:  accounts,
	}, nil
}

func createDefaultAccounts(ctx context.Context, am *keybase.ActionManager, enclaveID int64, pubKeyBytes []byte) ([]types.AccountInfo, error) {
	chains := []string{"bitcoin", "ethereum", "sonr"}
	derivedAccounts, err := bip44.DeriveAccounts(pubKeyBytes, chains)
	if err != nil {
		return nil, fmt.Errorf("derive accounts: %w", err)
	}

	accounts := make([]types.AccountInfo, 0, len(derivedAccounts))
	for i, derived := range derivedAccounts {
		isDefault := int64(0)
		if i == 0 {
			isDefault = 1
		}

		acc, err := am.CreateAccount(ctx, keybase.NewAccountInput{
			EnclaveID:    enclaveID,
			Address:      derived.Address,
			ChainID:      derived.ChainID,
			CoinType:     int64(derived.CoinType),
			AccountIndex: int64(derived.AccountIndex),
			AddressIndex: int64(derived.AddressIndex),
			Label:        derived.ChainID,
			IsDefault:    isDefault,
		})
		if err != nil {
			pdk.Log(pdk.LogWarn, fmt.Sprintf("createDefaultAccounts: failed for %s: %s", derived.ChainID, err))
			continue
		}

		accounts = append(accounts, types.AccountInfo{
			Address:  acc.Address,
			ChainID:  acc.ChainID,
			CoinType: acc.CoinType,
		})
	}

	return accounts, nil
}

func serializeDatabase() ([]byte, error) {
	kb := keybase.Get()
	if kb == nil {
		return nil, errors.New("database not initialized")
	}
	return kb.Serialize()
}

func loadDatabase(data []byte) (string, error) {
	if len(data) < 10 {
		return "", errors.New("invalid database format")
	}

	kb, err := keybase.Open()
	if err != nil {
		return "", fmt.Errorf("open database: %w", err)
	}

	ctx := context.Background()
	did, err := kb.Load(ctx, data)
	if err != nil {
		return "", fmt.Errorf("load DID: %w", err)
	}

	pdk.Log(pdk.LogDebug, "loadDatabase: database loaded successfully")
	return did, nil
}

func parseFilter(filter string) (*types.FilterParams, error) {
	params := &types.FilterParams{}
	parts := strings.FieldsSeq(filter)

	for part := range parts {
		kv := strings.SplitN(part, ":", 2)
		if len(kv) != 2 {
			continue
		}

		key, value := kv[0], kv[1]
		switch key {
		case "resource":
			params.Resource = value
		case "action":
			params.Action = value
		case "subject":
			params.Subject = value
		}
	}

	if params.Resource == "" {
		return nil, errors.New("resource is required")
	}
	if params.Action == "" {
		return nil, errors.New("action is required")
	}

	return params, nil
}

func validateUCAN(token string, params *types.FilterParams) error {
	if token == "" {
		return errors.New("token is required")
	}

	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		return errors.New("invalid token format: expected JWT with 3 parts")
	}

	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return fmt.Errorf("invalid token payload: %w", err)
	}

	var claims map[string]any
	if err := json.Unmarshal(payload, &claims); err != nil {
		return fmt.Errorf("invalid token claims: %w", err)
	}

	if exp, ok := claims["exp"].(float64); ok {
		if int64(exp) < currentUnixTime() {
			return errors.New("token has expired")
		}
	}

	if nbf, ok := claims["nbf"].(float64); ok {
		if int64(nbf) > currentUnixTime() {
			return errors.New("token is not yet valid")
		}
	}

	if aud, ok := claims["aud"].(string); ok {
		currentDID := state.GetDID()
		if currentDID != "" && aud != currentDID {
			pdk.Log(pdk.LogDebug, fmt.Sprintf("validateUCAN: audience mismatch, expected %s got %s", currentDID, aud))
		}
	}

	if att, ok := claims["att"].([]any); ok {
		if !checkAttenuations(att, params.Resource, params.Action) {
			return fmt.Errorf("token does not grant capability for %s:%s", params.Resource, params.Action)
		}
	}

	am, err := keybase.NewActionManager()
	if err == nil {
		if cid, ok := claims["cid"].(string); ok {
			ctx := context.Background()
			revoked, err := am.IsDelegationRevoked(ctx, cid)
			if err == nil && revoked {
				return errors.New("token has been revoked")
			}
		}
	}

	pdk.Log(pdk.LogDebug, fmt.Sprintf("validateUCAN: validated token for %s:%s", params.Resource, params.Action))
	return nil
}

func currentUnixTime() int64 {
	return 0
}

func checkAttenuations(attenuations []any, resource, action string) bool {
	for _, att := range attenuations {
		attMap, ok := att.(map[string]any)
		if !ok {
			continue
		}

		with, ok := attMap["with"].(string)
		if !ok {
			continue
		}

		if !matchResource(with, resource) {
			continue
		}

		can := attMap["can"]
		if canStr, ok := can.(string); ok {
			if canStr == "*" || canStr == action {
				return true
			}
		} else if canSlice, ok := can.([]any); ok {
			for _, c := range canSlice {
				if cStr, ok := c.(string); ok {
					if cStr == "*" || cStr == action {
						return true
					}
				}
			}
		}
	}
	return false
}

func matchResource(pattern, resource string) bool {
	if pattern == resource {
		return true
	}

	if strings.HasSuffix(pattern, "/*") {
		prefix := strings.TrimSuffix(pattern, "/*")
		return strings.HasPrefix(resource, prefix)
	}

	if strings.Contains(pattern, "://") {
		parts := strings.SplitN(pattern, "://", 2)
		if len(parts) == 2 && parts[1] == resource {
			return true
		}
	}

	return false
}

func executeAction(params *types.FilterParams) (json.RawMessage, error) {
	if params.Resource == "accounts" {
		switch params.Action {
		case "balances":
			return fetchAccountBalances(params.Subject)
		case "sign":
			return json.Marshal(map[string]string{"signature": "placeholder"})
		}
	}

	return keybase.Exec(context.Background(), params.Resource, params.Action, params.Subject)
}

func fetchAccountBalances(address string) (json.RawMessage, error) {
	if address == "" {
		address = state.GetDID()
	}

	apiBase, ok := state.GetConfig("api_endpoint")
	if !ok {
		apiBase = "https://api.sonr.io"
	}

	url := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s", apiBase, address)
	pdk.Log(pdk.LogInfo, fmt.Sprintf("fetchAccountBalances: GET %s", url))

	req := pdk.NewHTTPRequest(pdk.MethodGet, url)
	req.SetHeader("Accept", "application/json")

	res := req.Send()
	status := res.Status()

	if status < 200 || status >= 300 {
		pdk.Log(pdk.LogError, fmt.Sprintf("fetchAccountBalances: HTTP %d", status))
		return json.Marshal(map[string]any{
			"error":   "failed to fetch balances",
			"status":  status,
			"address": address,
		})
	}

	body := res.Body()
	pdk.Log(pdk.LogDebug, fmt.Sprintf("fetchAccountBalances: received %d bytes", len(body)))

	return body, nil
}

func resolveDID(did string) (*types.QueryOutput, error) {
	am, err := keybase.NewActionManager()
	if err != nil {
		return nil, fmt.Errorf("action manager: %w", err)
	}

	ctx := context.Background()
	doc, err := am.ResolveDID(ctx, did)
	if err != nil {
		return nil, fmt.Errorf("resolve DID: %w", err)
	}

	vms := make([]types.VerificationMethod, len(doc.VerificationMethods))
	for i, vm := range doc.VerificationMethods {
		vms[i] = types.VerificationMethod{
			ID:         vm.ID,
			Type:       vm.Type,
			Controller: vm.Controller,
			PublicKey:  vm.PublicKey,
			Purpose:    vm.Purpose,
		}
	}

	accounts := make([]types.Account, len(doc.Accounts))
	for i, acc := range doc.Accounts {
		accounts[i] = types.Account{
			Address:      acc.Address,
			ChainID:      acc.ChainID,
			CoinType:     int(acc.CoinType),
			AccountIndex: int(acc.AccountIndex),
			AddressIndex: int(acc.AddressIndex),
			Label:        acc.Label,
			IsDefault:    acc.IsDefault,
		}
	}

	credentials := make([]types.Credential, len(doc.Credentials))
	for i, cred := range doc.Credentials {
		credentials[i] = types.Credential{
			CredentialID:  cred.CredentialID,
			DeviceName:    cred.DeviceName,
			DeviceType:    cred.DeviceType,
			Authenticator: cred.Authenticator,
			Transports:    cred.Transports,
			CreatedAt:     cred.CreatedAt,
			LastUsed:      cred.LastUsed,
		}
	}

	return &types.QueryOutput{
		DID:                 doc.DID,
		Controller:          doc.Controller,
		VerificationMethods: vms,
		Accounts:            accounts,
		Credentials:         credentials,
	}, nil
}
