// Package tsslib provides TSS (Threshold Signature Scheme) functionality for Android
// This package is designed to be compiled with gomobile for Android integration via JNI
//
// Based on the verified tss-party implementation from service-party-app (Electron version)
package tsslib

import (
	"context"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math/big"
	"regexp"
	"strconv"
	"sync"
	"time"

	"github.com/bnb-chain/tss-lib/v2/common"
	"github.com/bnb-chain/tss-lib/v2/ecdsa/keygen"
	"github.com/bnb-chain/tss-lib/v2/ecdsa/signing"
	"github.com/bnb-chain/tss-lib/v2/tss"
)

// Regex to extract round number from tss-lib message type
// Message types look like: "binance.tsslib.ecdsa.keygen.KGRound1Message"
// or "binance.tsslib.ecdsa.signing.SignRound3Message"
var roundRegex = regexp.MustCompile(`Round(\d+)`)

// MessageCallback is the interface for receiving TSS protocol messages
// Android side implements this interface to handle message routing
type MessageCallback interface {
	// OnOutgoingMessage is called when TSS needs to send a message to other parties
	// messageJSON contains: type, isBroadcast, toParties, payload (base64)
	OnOutgoingMessage(messageJSON string)

	// OnProgress is called to report protocol progress
	OnProgress(round, totalRounds int)

	// OnError is called when an error occurs
	OnError(errorMessage string)

	// OnLog is called for debug logging
	OnLog(message string)
}

// Participant represents a party in the TSS protocol
type Participant struct {
	PartyID    string `json:"partyId"`
	PartyIndex int    `json:"partyIndex"`
}

// tssSession manages a TSS keygen or signing session
type tssSession struct {
	mu             sync.Mutex
	ctx            context.Context
	cancel         context.CancelFunc
	callback       MessageCallback
	localParty     tss.Party
	partyIndexMap  map[int]*tss.PartyID
	errCh          chan error
	keygenResultCh chan *keygen.LocalPartySaveData
	signResultCh   chan *common.SignatureData
	isKeygen       bool
}

var (
	currentSession *tssSession
	sessionMu      sync.Mutex
)

// StartKeygen initiates a new key generation session
// This is the entry point called from Android via JNI
func StartKeygen(
	sessionID, partyID string,
	partyIndex, thresholdT, thresholdN int,
	participantsJSON, password string,
	callback MessageCallback,
) error {
	sessionMu.Lock()
	defer sessionMu.Unlock()

	if currentSession != nil {
		return fmt.Errorf("a session is already in progress")
	}

	// Parse participants
	var participants []Participant
	if err := json.Unmarshal([]byte(participantsJSON), &participants); err != nil {
		return fmt.Errorf("failed to parse participants: %w", err)
	}

	if len(participants) != thresholdN {
		return fmt.Errorf("participant count mismatch: got %d, expected %d", len(participants), thresholdN)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)

	session := &tssSession{
		ctx:            ctx,
		cancel:         cancel,
		callback:       callback,
		partyIndexMap:  make(map[int]*tss.PartyID),
		errCh:          make(chan error, 1),
		keygenResultCh: make(chan *keygen.LocalPartySaveData, 1),
		isKeygen:       true,
	}

	// Create TSS party IDs - same as verified Electron version
	tssPartyIDs := make([]*tss.PartyID, len(participants))
	var selfTSSID *tss.PartyID

	for i, p := range participants {
		partyKey := tss.NewPartyID(
			p.PartyID,
			fmt.Sprintf("party-%d", p.PartyIndex),
			big.NewInt(int64(p.PartyIndex+1)),
		)
		tssPartyIDs[i] = partyKey
		if p.PartyID == partyID {
			selfTSSID = partyKey
		}
	}

	if selfTSSID == nil {
		cancel()
		return fmt.Errorf("self party not found in participants")
	}

	// Sort party IDs
	sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)

	// Build party index map for incoming messages
	for _, p := range sortedPartyIDs {
		for _, orig := range participants {
			if orig.PartyID == p.Id {
				session.partyIndexMap[orig.PartyIndex] = p
				break
			}
		}
	}

	// Create peer context and parameters
	// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
	// User says "2-of-3" meaning 2 signers needed, so we pass (thresholdT-1) to TSS-lib
	// For 2-of-3: thresholdT=2, tss-lib threshold=1, signers_needed=1+1=2 ✓
	peerCtx := tss.NewPeerContext(sortedPartyIDs)
	tssThreshold := thresholdT - 1
	params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)

	callback.OnLog(fmt.Sprintf("[TSS-KEYGEN] NewParameters: partyCount=%d, tssThreshold=%d (from thresholdT=%d, means %d signers needed)",
		len(sortedPartyIDs), tssThreshold, thresholdT, thresholdT))

	// Create channels
	outCh := make(chan tss.Message, thresholdN*10)
	endCh := make(chan *keygen.LocalPartySaveData, 1)

	// Create local party
	localParty := keygen.NewLocalParty(params, outCh, endCh)
	session.localParty = localParty

	// Start the local party
	go func() {
		if err := localParty.Start(); err != nil {
			session.errCh <- err
		}
	}()

	// Handle outgoing messages
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case msg, ok := <-outCh:
				if !ok {
					return
				}
				session.handleOutgoingMessage(msg)
			}
		}
	}()

	// Handle completion
	go func() {
		select {
		case <-ctx.Done():
			callback.OnError("session timeout or cancelled")
		case err := <-session.errCh:
			callback.OnError(fmt.Sprintf("keygen error: %v", err))
		case saveData := <-endCh:
			session.keygenResultCh <- saveData
		}
	}()

	currentSession = session
	return nil
}

// StartSign initiates a new signing session
// Based on verified executeSign from Electron version
func StartSign(
	sessionID, partyID string,
	partyIndex, thresholdT, thresholdN int,
	participantsJSON, messageHashHex, shareDataBase64, password string,
	callback MessageCallback,
) error {
	sessionMu.Lock()
	defer sessionMu.Unlock()

	if currentSession != nil {
		return fmt.Errorf("a session is already in progress")
	}

	// Parse participants
	var participants []Participant
	if err := json.Unmarshal([]byte(participantsJSON), &participants); err != nil {
		return fmt.Errorf("failed to parse participants: %w", err)
	}

	// Note: For signing, participant count equals threshold T (not N)
	// because only T parties participate in signing
	if len(participants) != thresholdT {
		return fmt.Errorf("participant count mismatch: got %d, expected %d (threshold T)", len(participants), thresholdT)
	}

	// Decode and decrypt share data
	encryptedShare, err := base64.StdEncoding.DecodeString(shareDataBase64)
	if err != nil {
		return fmt.Errorf("failed to decode share data: %w", err)
	}

	shareBytes, err := decryptShare(encryptedShare, password)
	if err != nil {
		return fmt.Errorf("failed to decrypt share: %w", err)
	}

	// Parse keygen save data
	var keygenData keygen.LocalPartySaveData
	if err := json.Unmarshal(shareBytes, &keygenData); err != nil {
		return fmt.Errorf("failed to parse keygen data: %w", err)
	}

	// Decode message hash
	messageHash, err := hex.DecodeString(messageHashHex)
	if err != nil {
		return fmt.Errorf("failed to decode message hash: %w", err)
	}

	if len(messageHash) != 32 {
		return fmt.Errorf("message hash must be 32 bytes, got %d", len(messageHash))
	}

	msgBigInt := new(big.Int).SetBytes(messageHash)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)

	session := &tssSession{
		ctx:           ctx,
		cancel:        cancel,
		callback:      callback,
		partyIndexMap: make(map[int]*tss.PartyID),
		errCh:         make(chan error, 1),
		signResultCh:  make(chan *common.SignatureData, 1),
		isKeygen:      false,
	}

	// Create TSS party IDs for signing participants
	// IMPORTANT: For tss-lib signing, we must reconstruct the party IDs in the same way
	// as during keygen. The signing subset (T parties) must use their original keys from keygen.
	tssPartyIDs := make([]*tss.PartyID, 0, len(participants))
	var selfTSSID *tss.PartyID

	for _, p := range participants {
		partyKey := tss.NewPartyID(
			p.PartyID,
			fmt.Sprintf("party-%d", p.PartyIndex),
			big.NewInt(int64(p.PartyIndex+1)),
		)
		tssPartyIDs = append(tssPartyIDs, partyKey)
		if p.PartyID == partyID {
			selfTSSID = partyKey
		}
	}

	if selfTSSID == nil {
		cancel()
		return fmt.Errorf("self party not found in participants")
	}

	// Sort party IDs (important for tss-lib)
	sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)

	// Build party index map for incoming messages
	for _, p := range sortedPartyIDs {
		for _, orig := range participants {
			if orig.PartyID == p.Id {
				session.partyIndexMap[orig.PartyIndex] = p
				break
			}
		}
	}

	// Create peer context and parameters
	// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
	// This MUST match keygen exactly!
	peerCtx := tss.NewPeerContext(sortedPartyIDs)
	tssThreshold := thresholdT - 1
	params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)

	callback.OnLog(fmt.Sprintf("[TSS-SIGN] NewParameters: partyCount=%d, tssThreshold=%d (from thresholdT=%d, means %d signers needed)",
		len(sortedPartyIDs), tssThreshold, thresholdT, thresholdT))

	callback.OnLog(fmt.Sprintf("[TSS-SIGN] Original keygenData has %d parties (Ks length)", len(keygenData.Ks)))
	callback.OnLog(fmt.Sprintf("[TSS-SIGN] Building subset for %d signing parties", len(sortedPartyIDs)))

	// CRITICAL: Build a subset of the keygen save data for the current signing parties
	// This is required when signing with a subset of the original keygen participants.
	subsetKeygenData := keygen.BuildLocalSaveDataSubset(keygenData, sortedPartyIDs)
	callback.OnLog(fmt.Sprintf("[TSS-SIGN] Subset keygenData has %d parties (Ks length)", len(subsetKeygenData.Ks)))

	// Create channels
	outCh := make(chan tss.Message, thresholdT*10)
	endCh := make(chan *common.SignatureData, 1)

	// Create local party for signing with the SUBSET keygen data
	localParty := signing.NewLocalParty(msgBigInt, params, subsetKeygenData, outCh, endCh)
	session.localParty = localParty

	// Start the local party
	go func() {
		if err := localParty.Start(); err != nil {
			session.errCh <- err
		}
	}()

	// Handle outgoing messages
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case msg, ok := <-outCh:
				if !ok {
					return
				}
				session.handleOutgoingMessage(msg)
			}
		}
	}()

	// Handle completion
	go func() {
		select {
		case <-ctx.Done():
			callback.OnError("session timeout or cancelled")
		case err := <-session.errCh:
			callback.OnError(fmt.Sprintf("sign error: %v", err))
		case sigData := <-endCh:
			session.signResultCh <- sigData
		}
	}()

	currentSession = session
	return nil
}

// SendIncomingMessage delivers a message from another party to the current session
func SendIncomingMessage(fromPartyIndex int, isBroadcast bool, payloadBase64 string) error {
	sessionMu.Lock()
	session := currentSession
	sessionMu.Unlock()

	if session == nil {
		return fmt.Errorf("no active session")
	}

	session.mu.Lock()
	defer session.mu.Unlock()

	fromParty, ok := session.partyIndexMap[fromPartyIndex]
	if !ok {
		return fmt.Errorf("unknown party index: %d", fromPartyIndex)
	}

	payload, err := base64.StdEncoding.DecodeString(payloadBase64)
	if err != nil {
		return fmt.Errorf("failed to decode payload: %w", err)
	}

	parsedMsg, err := tss.ParseWireMessage(payload, fromParty, isBroadcast)
	if err != nil {
		return fmt.Errorf("failed to parse message: %w", err)
	}

	// Extract round from incoming message and update progress
	// This ensures progress updates on both sending and receiving messages
	totalRounds := 4 // GG20 keygen has 4 rounds
	if !session.isKeygen {
		totalRounds = 9 // GG20 signing has 9 rounds
	}
	currentRound := extractRoundFromMessageType(parsedMsg.Type())
	if currentRound > 0 {
		session.callback.OnProgress(currentRound, totalRounds)
	}

	go func() {
		_, err := session.localParty.Update(parsedMsg)
		if err != nil {
			// Only send fatal errors
			if !isDuplicateError(err) {
				session.errCh <- err
			}
		}
	}()

	return nil
}

// WaitForKeygenResult blocks until keygen completes and returns the result as JSON
func WaitForKeygenResult(password string) (string, error) {
	sessionMu.Lock()
	session := currentSession
	sessionMu.Unlock()

	if session == nil {
		return "", fmt.Errorf("no active session")
	}

	if !session.isKeygen {
		return "", fmt.Errorf("current session is not a keygen session")
	}

	// Track progress - GG20 keygen has 4 rounds
	totalRounds := 4

	select {
	case <-session.ctx.Done():
		return "", session.ctx.Err()
	case saveData := <-session.keygenResultCh:
		// Keygen completed successfully
		session.callback.OnProgress(totalRounds, totalRounds)

		// Get public key - same as Electron version
		pubKey := saveData.ECDSAPub.ToECDSAPubKey()
		pubKeyBytes := make([]byte, 33)
		pubKeyBytes[0] = 0x02 + byte(pubKey.Y.Bit(0))
		xBytes := pubKey.X.Bytes()
		copy(pubKeyBytes[33-len(xBytes):], xBytes)

		// Serialize and encrypt save data
		saveDataBytes, err := json.Marshal(saveData)
		if err != nil {
			return "", fmt.Errorf("failed to serialize save data: %w", err)
		}

		// Encrypt with password (same as Electron version)
		encryptedShare := encryptShare(saveDataBytes, password)

		result := struct {
			PublicKey      string `json:"publicKey"`
			EncryptedShare string `json:"encryptedShare"`
		}{
			PublicKey:      base64.StdEncoding.EncodeToString(pubKeyBytes),
			EncryptedShare: base64.StdEncoding.EncodeToString(encryptedShare),
		}

		resultJSON, _ := json.Marshal(result)

		// Clean up session
		session.cancel()
		sessionMu.Lock()
		currentSession = nil
		sessionMu.Unlock()

		return string(resultJSON), nil
	}
}

// WaitForSignResult blocks until signing completes and returns the result as JSON
func WaitForSignResult() (string, error) {
	sessionMu.Lock()
	session := currentSession
	sessionMu.Unlock()

	if session == nil {
		return "", fmt.Errorf("no active session")
	}

	if session.isKeygen {
		return "", fmt.Errorf("current session is not a sign session")
	}

	// Track progress - GG20 signing has 9 rounds
	totalRounds := 9

	select {
	case <-session.ctx.Done():
		return "", session.ctx.Err()
	case sigData := <-session.signResultCh:
		// Signing completed successfully
		session.callback.OnProgress(totalRounds, totalRounds)

		// Construct signature: R (32 bytes) || S (32 bytes)
		rBytes := sigData.R
		sBytes := sigData.S

		signature := make([]byte, 64)
		copy(signature[32-len(rBytes):32], rBytes)
		copy(signature[64-len(sBytes):64], sBytes)

		// Recovery ID for Ethereum-style signatures
		recoveryID := int(sigData.SignatureRecovery[0])

		// Append recovery ID to signature (r + s + v = 64 + 1 = 65 bytes)
		// This is needed for EVM transaction signing
		signatureWithV := make([]byte, 65)
		copy(signatureWithV, signature)
		signatureWithV[64] = byte(recoveryID)

		result := struct {
			Signature  string `json:"signature"`
			RecoveryID int    `json:"recoveryId"`
		}{
			Signature:  base64.StdEncoding.EncodeToString(signatureWithV),
			RecoveryID: recoveryID,
		}

		resultJSON, _ := json.Marshal(result)

		// Clean up session
		session.cancel()
		sessionMu.Lock()
		currentSession = nil
		sessionMu.Unlock()

		return string(resultJSON), nil
	}
}

// CancelSession cancels the current session
func CancelSession() {
	sessionMu.Lock()
	defer sessionMu.Unlock()

	if currentSession != nil {
		currentSession.cancel()
		currentSession = nil
	}
}

// extractRoundFromMessageType parses the round number from a tss-lib message type string.
// Returns 0 if parsing fails (safe fallback).
// Example: "binance.tsslib.ecdsa.keygen.KGRound2Message1" -> 2
func extractRoundFromMessageType(msgType string) int {
	matches := roundRegex.FindStringSubmatch(msgType)
	if len(matches) >= 2 {
		if round, err := strconv.Atoi(matches[1]); err == nil {
			return round
		}
	}
	return 0 // Safe fallback - doesn't affect protocol, just shows 0 in UI
}

func (s *tssSession) handleOutgoingMessage(msg tss.Message) {
	msgBytes, _, err := msg.WireBytes()
	if err != nil {
		return
	}

	var toParties []string
	if !msg.IsBroadcast() {
		for _, to := range msg.GetTo() {
			toParties = append(toParties, to.Id)
		}
	}

	outMsg := struct {
		Type        string   `json:"type"`
		IsBroadcast bool     `json:"isBroadcast"`
		ToParties   []string `json:"toParties,omitempty"`
		Payload     string   `json:"payload"`
	}{
		Type:        "outgoing",
		IsBroadcast: msg.IsBroadcast(),
		ToParties:   toParties,
		Payload:     base64.StdEncoding.EncodeToString(msgBytes),
	}

	data, _ := json.Marshal(outMsg)
	s.callback.OnOutgoingMessage(string(data))

	// Extract current round from message type and send progress update
	totalRounds := 4 // GG20 keygen has 4 rounds
	if !s.isKeygen {
		totalRounds = 9 // GG20 signing has 9 rounds
	}
	currentRound := extractRoundFromMessageType(msg.Type())
	s.callback.OnProgress(currentRound, totalRounds)
}

func isDuplicateError(err error) bool {
	if err == nil {
		return false
	}
	errStr := err.Error()
	return contains(errStr, "duplicate") || contains(errStr, "already received")
}

func contains(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}

// encryptShare encrypts the share data with password
// Same implementation as Electron version for compatibility
func encryptShare(data []byte, password string) []byte {
	// TODO: Use proper AES-256-GCM encryption
	// For now, just prepend a marker and the password hash
	// This is NOT secure - just a placeholder (same as Electron version)
	result := make([]byte, len(data)+32)
	copy(result[:32], hashPassword(password))
	copy(result[32:], data)
	return result
}

// decryptShare decrypts the share data with password
// Same implementation as Electron version for compatibility
func decryptShare(encryptedData []byte, password string) ([]byte, error) {
	// Match the encryption format: first 32 bytes are password hash, rest is data
	if len(encryptedData) < 32 {
		return nil, fmt.Errorf("encrypted data too short")
	}

	// Verify password (simple check - matches encryptShare)
	expectedHash := hashPassword(password)
	actualHash := encryptedData[:32]

	// Simple comparison
	match := true
	for i := 0; i < 32; i++ {
		if expectedHash[i] != actualHash[i] {
			match = false
			break
		}
	}

	if !match {
		return nil, fmt.Errorf("incorrect password")
	}

	return encryptedData[32:], nil
}

// hashPassword creates a simple hash of the password
// Same implementation as Electron version for compatibility
func hashPassword(password string) []byte {
	// Simple hash - should use PBKDF2 or Argon2 in production
	hash := make([]byte, 32)
	for i := 0; i < len(password) && i < 32; i++ {
		hash[i] = password[i]
	}
	return hash
}
