/**
 * 🔐 PURE JavaScript Taproot Signer - NO WASM!
 * 
 * LEI PRIMORDIAL: O mnemonic JAMAIS sai do dispositivo!
 * 
 * This module uses ONLY pure JavaScript libraries:
 * - @noble/secp256k1 (pure JS Schnorr signatures)
 * - @noble/hashes (pure JS SHA256)
 * - bip39 (pure JS mnemonic)
 * 
 * NO WASM dependencies! Works in Service Workers and Popups!
 */

import * as bip39 from 'bip39';
import { sha256 } from '@noble/hashes/sha2.js';
import { hmac } from '@noble/hashes/hmac.js';
import { schnorr, getPublicKey as getFullPublicKey, hashes } from '@noble/secp256k1';
import * as bitcoin from 'bitcoinjs-lib';

// 🔧 CRITICAL: Configure @noble/secp256k1 with hash functions
// v3 requires explicit hash function configuration for SYNC operations
// The library checks hashes.sha256 and hashes.hmacSha256 for sync signing
hashes.hmacSha256 = (key, ...msgs) => {
  const h = hmac.create(sha256, key);
  msgs.forEach(msg => h.update(msg));
  return h.digest();
};
hashes.sha256 = (...msgs) => {
  const h = sha256.create();
  msgs.forEach(msg => h.update(msg));
  return h.digest();
};

console.log('🔐 @noble/secp256k1 hash functions configured (PURE JS)!');

// ==========================================
// BIP32 IMPLEMENTATION (PURE JS - NO WASM!)
// ==========================================

const BITCOIN_SEED = 'Bitcoin seed';
const HARDENED_OFFSET = 0x80000000;
const N = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141');

/**
 * HMAC-SHA512 using @noble/hashes
 */
function hmacSha512(key, data) {
    // @noble/hashes hmac only does sha256, we need sha512
    // Use a simple implementation
    const crypto = globalThis.crypto || require('crypto');
    
    if (typeof crypto.subtle !== 'undefined') {
        // Browser - use SubtleCrypto
        return hmacSha512Browser(key, data);
    }
    
    // Fallback to simple HMAC-SHA512
    return hmacSha512Simple(key, data);
}

/**
 * Simple HMAC-SHA512 for browser (async)
 */
async function hmacSha512Browser(key, data) {
    const cryptoKey = await crypto.subtle.importKey(
        'raw',
        key,
        { name: 'HMAC', hash: 'SHA-512' },
        false,
        ['sign']
    );
    const sig = await crypto.subtle.sign('HMAC', cryptoKey, data);
    return new Uint8Array(sig);
}

/**
 * Derive master key from seed (BIP32)
 */
async function deriveMasterKey(seed) {
    const key = new TextEncoder().encode(BITCOIN_SEED);
    const I = await hmacSha512Browser(key, seed);
    
    return {
        privateKey: I.slice(0, 32),
        chainCode: I.slice(32)
    };
}

/**
 * Derive child key (BIP32)
 */
async function deriveChild(parentKey, parentChainCode, index) {
    const isHardened = index >= HARDENED_OFFSET;
    
    let data;
    if (isHardened) {
        // Hardened: 0x00 || privateKey || index
        data = new Uint8Array(37);
        data[0] = 0;
        data.set(parentKey, 1);
        new DataView(data.buffer).setUint32(33, index, false);
    } else {
        // Normal: publicKey || index
        const pubKey = getCompressedPublicKey(parentKey);
        data = new Uint8Array(37);
        data.set(pubKey, 0);
        new DataView(data.buffer).setUint32(33, index, false);
    }
    
    const I = await hmacSha512Browser(parentChainCode, data);
    const IL = I.slice(0, 32);
    const IR = I.slice(32);
    
    // childKey = (IL + parentKey) mod N
    const ilBigInt = bytesToBigInt(IL);
    const parentBigInt = bytesToBigInt(parentKey);
    const childBigInt = (ilBigInt + parentBigInt) % N;
    
    return {
        privateKey: bigIntToBytes(childBigInt, 32),
        chainCode: IR
    };
}

/**
 * Derive BIP86 key path: m/86'/0'/0'/0/0
 */
async function deriveBip86Key(mnemonic) {
    console.log('🔐 Deriving BIP86 key (pure JS)...');
    
    // 1. Mnemonic to seed
    const seed = await bip39.mnemonicToSeed(mnemonic);
    
    // 2. Master key
    let { privateKey, chainCode } = await deriveMasterKey(new Uint8Array(seed));
    
    // 3. Derive path: m/86'/0'/0'/0/0
    const path = [
        86 + HARDENED_OFFSET,  // 86'
        0 + HARDENED_OFFSET,   // 0'
        0 + HARDENED_OFFSET,   // 0'
        0,                      // 0
        0                       // 0
    ];
    
    for (const index of path) {
        const child = await deriveChild(privateKey, chainCode, index);
        privateKey = child.privateKey;
        chainCode = child.chainCode;
    }
    
    console.log('   ✅ BIP86 key derived (pure JS)');
    return privateKey;
}

// ==========================================
// SECP256K1 HELPERS (PURE JS)
// ==========================================

/**
 * Get compressed public key from private key
 */
function getCompressedPublicKey(privateKey) {
    // Use @noble/secp256k1 getPublicKey (returns 33-byte compressed by default)
    return getFullPublicKey(privateKey, true);
}

/**
 * Get x-only public key (32 bytes) for Schnorr/Taproot
 */
function getXOnlyPublicKey(privateKey) {
    // schnorr.getPublicKey returns x-only 32-byte pubkey
    return schnorr.getPublicKey(privateKey);
}

/**
 * Bytes to BigInt
 */
function bytesToBigInt(bytes) {
    let result = BigInt(0);
    for (let i = 0; i < bytes.length; i++) {
        result = (result << BigInt(8)) + BigInt(bytes[i]);
    }
    return result;
}

/**
 * BigInt to bytes
 */
function bigIntToBytes(num, length) {
    const result = new Uint8Array(length);
    for (let i = length - 1; i >= 0; i--) {
        result[i] = Number(num & BigInt(0xff));
        num = num >> BigInt(8);
    }
    return result;
}

// ==========================================
// TAPROOT SIGNING
// ==========================================

/**
 * Tagged hash (BIP340/341)
 */
function taggedHash(tag, data) {
    const tagHash = sha256(new TextEncoder().encode(tag));
    const combined = new Uint8Array(tagHash.length * 2 + data.length);
    combined.set(tagHash, 0);
    combined.set(tagHash, tagHash.length);
    combined.set(data, tagHash.length * 2);
    return sha256(combined);
}

/**
 * Apply Taproot tweak to private key
 */
function applyTaprootTweak(privateKey, internalPubkey) {
    const tweakHash = taggedHash('TapTweak', internalPubkey);
    
    // Check if pubkey has odd Y
    const fullPubkey = getCompressedPublicKey(privateKey);
    const hasOddY = fullPubkey[0] === 0x03;
    
    let adjustedPrivKey = privateKey;
    if (hasOddY) {
        const keyBigInt = bytesToBigInt(privateKey);
        const negated = N - keyBigInt;
        adjustedPrivKey = bigIntToBytes(negated, 32);
    }
    
    // Add tweak
    const tweakBigInt = bytesToBigInt(tweakHash);
    const keyBigInt = bytesToBigInt(adjustedPrivKey);
    const result = (keyBigInt + tweakBigInt) % N;
    
    return bigIntToBytes(result, 32);
}

/**
 * Calculate Taproot sighash (BIP341)
 */
function calculateTaprootSighash(psbt, inputIndex, hashType) {
    const tx = psbt.data.globalMap.unsignedTx;
    const input = psbt.data.inputs[inputIndex];
    
    if (!input.witnessUtxo) {
        throw new Error('Input missing witnessUtxo');
    }
    
    const prevoutScripts = psbt.data.inputs.map(inp => inp.witnessUtxo.script);
    const prevoutValues = psbt.data.inputs.map(inp => BigInt(inp.witnessUtxo.value));
    
    // Get raw transaction
    const txBuf = tx.toBuffer();
    const txObj = bitcoin.Transaction.fromBuffer(txBuf);
    
    const parts = [];
    
    // Epoch (1 byte)
    parts.push(new Uint8Array([0x00]));
    
    // Hash type (1 byte)
    parts.push(new Uint8Array([hashType]));
    
    // Version (4 bytes)
    const versionBuf = new Uint8Array(4);
    new DataView(versionBuf.buffer).setInt32(0, txObj.version, true);
    parts.push(versionBuf);
    
    // Locktime (4 bytes)
    const locktimeBuf = new Uint8Array(4);
    new DataView(locktimeBuf.buffer).setUint32(0, txObj.locktime, true);
    parts.push(locktimeBuf);
    
    // Extract sighash base type (without ANYONECANPAY flag)
    const sighashBase = hashType & 0x03;
    const anyoneCanPay = (hashType & 0x80) !== 0;
    
    // For non-ANYONECANPAY, include precomputed hashes
    if (!anyoneCanPay) {
        // sha256(prevouts)
        const prevoutsData = [];
        for (const inp of txObj.ins) {
            const buf = new Uint8Array(36);
            buf.set(inp.hash, 0);
            new DataView(buf.buffer).setUint32(32, inp.index, true);
            prevoutsData.push(buf);
        }
        parts.push(sha256(concatBytes(prevoutsData)));
        
        // sha256(amounts)
        const amountsData = prevoutValues.map(v => {
            const buf = new Uint8Array(8);
            const dv = new DataView(buf.buffer);
            dv.setUint32(0, Number(v & BigInt(0xffffffff)), true);
            dv.setUint32(4, Number((v >> BigInt(32)) & BigInt(0xffffffff)), true);
            return buf;
        });
        parts.push(sha256(concatBytes(amountsData)));
        
        // sha256(scriptPubkeys)
        const scriptsData = prevoutScripts.map(script => {
            const len = new Uint8Array([script.length]);
            return concatBytes([len, script]);
        });
        parts.push(sha256(concatBytes(scriptsData)));
        
        // sha256(sequences) - SKIP for SIGHASH_SINGLE and SIGHASH_NONE!
        if (sighashBase !== 2 && sighashBase !== 3) {
            const seqData = txObj.ins.map(inp => {
                const buf = new Uint8Array(4);
                new DataView(buf.buffer).setUint32(0, inp.sequence, true);
                return buf;
            });
            parts.push(sha256(concatBytes(seqData)));
        }
    }
    
    // Handle outputs based on SIGHASH type
    // ⚠️ CRITICAL BIP341: For SIGHASH_SINGLE, sha_single_output comes AFTER input data (at the very end)!
    if (sighashBase === 0 || sighashBase === 1) {
        // SIGHASH_DEFAULT (0x00) or SIGHASH_ALL (0x01): include all outputs
        const outsData = txObj.outs.map(out => {
            const valueBuf = new Uint8Array(8);
            const dv = new DataView(valueBuf.buffer);
            const v = BigInt(out.value);
            dv.setUint32(0, Number(v & BigInt(0xffffffff)), true);
            dv.setUint32(4, Number((v >> BigInt(32)) & BigInt(0xffffffff)), true);
            const scriptLen = new Uint8Array([out.script.length]);
            return concatBytes([valueBuf, scriptLen, out.script]);
        });
        parts.push(sha256(concatBytes(outsData)));
    }
    // SIGHASH_NONE (0x02) and SIGHASH_SINGLE (0x03): outputs hash is NOT added here!
    // For SIGHASH_SINGLE, it will be added at the end after input data
    
    // Spend type (1 byte) - 0x00 for key-path
    parts.push(new Uint8Array([0x00]));
    
    // Input data - depends on ANYONECANPAY flag
    if (anyoneCanPay) {
        const inp = txObj.ins[inputIndex];
        
        // outpoint
        const prevout = new Uint8Array(36);
        prevout.set(inp.hash, 0);
        new DataView(prevout.buffer).setUint32(32, inp.index, true);
        parts.push(prevout);
        
        // amount
        const amountBuf = new Uint8Array(8);
        const v = prevoutValues[inputIndex];
        const dv = new DataView(amountBuf.buffer);
        dv.setUint32(0, Number(v & BigInt(0xffffffff)), true);
        dv.setUint32(4, Number((v >> BigInt(32)) & BigInt(0xffffffff)), true);
        parts.push(amountBuf);
        
        // scriptPubkey
        const script = prevoutScripts[inputIndex];
        const scriptLen = new Uint8Array([script.length]);
        parts.push(concatBytes([scriptLen, script]));
        
        // sequence
        const seqBuf = new Uint8Array(4);
        new DataView(seqBuf.buffer).setUint32(0, inp.sequence, true);
        parts.push(seqBuf);
    } else {
        // input index (4 bytes LE)
        const idxBuf = new Uint8Array(4);
        new DataView(idxBuf.buffer).setUint32(0, inputIndex, true);
        parts.push(idxBuf);
    }
    
    // ⚠️ CRITICAL BIP341: sha_single_output comes LAST for SIGHASH_SINGLE!
    if (sighashBase === 3) {
        // SIGHASH_SINGLE: hash only the output at inputIndex
        if (inputIndex < txObj.outs.length) {
            const out = txObj.outs[inputIndex];
            const valueBuf = new Uint8Array(8);
            const dv = new DataView(valueBuf.buffer);
            const v = BigInt(out.value);
            dv.setUint32(0, Number(v & BigInt(0xffffffff)), true);
            dv.setUint32(4, Number((v >> BigInt(32)) & BigInt(0xffffffff)), true);
            const scriptLen = new Uint8Array([out.script.length]);
            const singleOutput = concatBytes([valueBuf, scriptLen, out.script]);
            parts.push(sha256(singleOutput));
        } else {
            // If inputIndex >= outputs.length, use zero hash (BIP341)
            parts.push(new Uint8Array(32));
        }
    }
    
    // TapSighash tagged hash
    return taggedHash('TapSighash', concatBytes(parts));
}

/**
 * Concatenate byte arrays
 */
function concatBytes(arrays) {
    const totalLen = arrays.reduce((acc, arr) => acc + arr.length, 0);
    const result = new Uint8Array(totalLen);
    let offset = 0;
    for (const arr of arrays) {
        result.set(arr, offset);
        offset += arr.length;
    }
    return result;
}

// ==========================================
// MAIN SIGNING FUNCTION
// ==========================================

/**
 * Sign PSBT locally using pure JavaScript (NO WASM!)
 * 
 * @param {string} mnemonic - 12/24 word mnemonic
 * @param {string} psbtBase64 - PSBT in base64 format
 * @param {object} options - { inputsToSign, sighashType, autoFinalize }
 */
async function signPsbtTaprootLocal(mnemonic, psbtBase64, options = {}) {
    const {
        inputsToSign = null,
        sighashType = 0x00,
        autoFinalize = true
    } = options;
    
    console.log('🔐 [PURE JS SIGNER] Starting local Taproot signing...');
    console.log('   LEI PRIMORDIAL: Mnemonic NEVER leaves device!');
    console.log('   SIGHASH: 0x' + sighashType.toString(16));
    
    try {
        // 1. Derive private key from mnemonic
        const privateKey = await deriveBip86Key(mnemonic);
        
        // 2. Get x-only public key
        const xOnlyPubkey = getXOnlyPublicKey(privateKey);
        
        // 3. Apply Taproot tweak
        const tweakedPrivKey = applyTaprootTweak(privateKey, xOnlyPubkey);
        
        console.log('   ✅ Taproot key tweaked');
        
        // 4. Parse PSBT
        const psbt = bitcoin.Psbt.fromBase64(psbtBase64);
        console.log('   📄 PSBT has', psbt.inputCount, 'inputs');
        
        // 5. Determine inputs to sign
        const indicesToSign = inputsToSign 
            ? (Array.isArray(inputsToSign) ? inputsToSign : [inputsToSign])
            : Array.from({ length: psbt.inputCount }, (_, i) => i);
        
        console.log('   🔏 Signing inputs:', indicesToSign);
        
        // 6. Sign each input
        for (const inputIndex of indicesToSign) {
            try {
                const input = psbt.data.inputs[inputIndex];
                
                // Check if input has witnessUtxo
                if (!input.witnessUtxo) {
                    console.warn(`   ⚠️ Input ${inputIndex} has no witnessUtxo, skipping`);
                    continue;
                }
                
                // Calculate sighash
                const sighash = calculateTaprootSighash(psbt, inputIndex, sighashType);
                console.log(`   🔏 Sighash[${inputIndex}]:`, bytesToHex(sighash).slice(0, 16) + '...');
                
                // Sign with Schnorr
                const signature = await schnorr.sign(sighash, tweakedPrivKey);
                console.log(`   ✅ Schnorr signature created for input ${inputIndex}`);
                
                // Add sighash type if not default
                let finalSig;
                if (sighashType === 0x00) {
                    finalSig = Buffer.from(signature);
                } else {
                    finalSig = Buffer.concat([Buffer.from(signature), Buffer.from([sighashType])]);
                }
                
                // Add signature using updateInput (same as mobile app)
                psbt.updateInput(inputIndex, { tapKeySig: finalSig });
                
                console.log(`   ✅ Input ${inputIndex} signed with tapKeySig`);
            } catch (err) {
                console.warn(`   ⚠️ Could not sign input ${inputIndex}:`, err.message);
            }
        }
        
        // 7. Finalize if requested
        if (autoFinalize) {
            for (const inputIndex of indicesToSign) {
                try {
                    psbt.finalizeInput(inputIndex);
                } catch (e) {
                    // May already be finalized
                }
            }
            console.log('   ✅ PSBT finalized');
        }
        
        // 8. Return signed PSBT
        const signedPsbt = psbt.toBase64();
        console.log('🔐 [PURE JS SIGNER] Signing complete!');
        
        return {
            signedPsbt,
            txHex: autoFinalize ? psbt.extractTransaction().toHex() : null
        };
        
    } catch (error) {
        console.error('❌ [PURE JS SIGNER] Error:', error.message);
        throw new Error(`Signing failed: ${error.message}`);
    }
}

/**
 * Bytes to hex string
 */
function bytesToHex(bytes) {
    return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}

// ==========================================
// EXPORTS
// ==========================================

// For UMD/browser
if (typeof window !== 'undefined') {
    window.TaprootSignerPure = {
        signPsbtTaprootLocal
    };
}

if (typeof self !== 'undefined') {
    self.TaprootSignerPure = {
        signPsbtTaprootLocal
    };
}

export {
    signPsbtTaprootLocal
};

export default {
    signPsbtTaprootLocal
};

