import { expect } from 'chai';
import { Transaction, Address, PrivateKey, Script, Networks } from '../../src/btc';
import { Point, utils } from '../../src/crypto/secp256k1';
import { signCetWithAdaptorSig, verifyCetAdaptorSig, verifyCetSignature, sigToTaprootBuf } from '../../src/cet/signature';
import { createCet } from '../../src/cet/transactions';
import { adaptSig } from '../../src/crypto/counterparty';
import { commitToEvent, attestEventOutcome } from '../../src';

describe('CET Signature', () => {
  let borrowerKey: PrivateKey;
  let lenderKey: PrivateKey;
  let borrowerAddress: Address;
  let lenderAddress: Address;
  let dlcUtxo: any;
  let cet: Transaction;
  let oraclePrivKey: Uint8Array;
  let oraclePubKey: Point;
  let eventOutcomeHashes: Uint8Array[];

  before(async () => {
    // Generate test keys
    oraclePrivKey = utils.randomPrivateKey();
    oraclePubKey = Point.fromPrivateKey(oraclePrivKey);
    borrowerKey = new PrivateKey();
    lenderKey = new PrivateKey();
    borrowerAddress = new Address(borrowerKey.toPublicKey(), Networks.testnet, 'taproot');
    lenderAddress = new Address(lenderKey.toPublicKey(), Networks.testnet, 'taproot');

    // Generate test event outcome hashes
    eventOutcomeHashes = [
      new Uint8Array([1, 2, 3]),
      new Uint8Array([4, 5, 6])
    ];

    // Create mock DLC UTXO
    dlcUtxo = {
      txId: 'a'.repeat(64),
      outputIndex: 0,
      satoshis: 100000, // 0.001 BTC
      script: new Script('')
    };

    cet = createCet(dlcUtxo, 60000, 40000, borrowerAddress, lenderAddress);
  });

  describe('signCetWithAdaptorSig and verifyCetAdaptorSig', () => {
    it('should create and verify a valid adaptor signature', async () => {
      const { signaturePoints, nonce } = await commitToEvent(eventOutcomeHashes, oraclePubKey);
      const outcomeIndex = 0;
      
      const counterpartyPrivKey = utils.randomPrivateKey();
      const counterpartyPubKey = Point.fromPrivateKey(counterpartyPrivKey);
      const adaptorSig = await signCetWithAdaptorSig(
        counterpartyPrivKey,
        signaturePoints[outcomeIndex],
        cet,
        0,
        Buffer.from('')
      );
      
      expect(adaptorSig).to.be.instanceOf(Object);
      expect(adaptorSig).to.have.property('R_prime');
      expect(adaptorSig).to.have.property('s_prime');
      expect(adaptorSig.R_prime).to.be.instanceOf(Point);
      expect(typeof adaptorSig.s_prime).to.equal('bigint');

      const isAdaptorSigValid = await verifyCetAdaptorSig(
        adaptorSig,
        counterpartyPubKey,
        signaturePoints[outcomeIndex],
        cet,
        0,
        Buffer.from('')
      );
      expect(isAdaptorSigValid).to.be.true;

      const oracleSig = await attestEventOutcome(
        oraclePrivKey,
        nonce,
        eventOutcomeHashes[outcomeIndex]
      );
      
      const completedSig = adaptSig(adaptorSig, oracleSig.s);

      const isValid = await verifyCetSignature(completedSig, counterpartyPubKey, cet, 0, Buffer.from(''));
      expect(isValid).to.be.true;
    });

    it('should detect invalid completed signatures', async () => {
      const { signaturePoints, nonce } = await commitToEvent(eventOutcomeHashes, oraclePubKey);
      const outcomeIndex = 0;
      
      const counterpartyPrivKey = utils.randomPrivateKey();
      const counterpartyPubKey = Point.fromPrivateKey(counterpartyPrivKey);
      const adaptorSig = await signCetWithAdaptorSig(
        counterpartyPrivKey,
        signaturePoints[outcomeIndex],
        cet,
        0,
        Buffer.from('')
      );

      const isAdaptorSigValid = await verifyCetAdaptorSig(
        adaptorSig,
        counterpartyPubKey,
        signaturePoints[outcomeIndex],
        cet,
        0,
        Buffer.from('')
      );
      expect(isAdaptorSigValid).to.be.true;

      const oracleSig = await attestEventOutcome(
        oraclePrivKey,
        nonce,
        eventOutcomeHashes[1] // Different outcome
      );
      
      const completedSig = adaptSig(adaptorSig, oracleSig.s);

      const isValid = await verifyCetSignature(completedSig, counterpartyPubKey, cet, 0, Buffer.from(''));
      expect(isValid).to.be.false;
    });

    it('should detect signatures for wrong messages', async () => {
      const { signaturePoints, nonce } = await commitToEvent(eventOutcomeHashes, oraclePubKey);
      const outcomeIndex = 0;
      
      const counterpartyPrivKey = utils.randomPrivateKey();
      const counterpartyPubKey = Point.fromPrivateKey(counterpartyPrivKey);
      const adaptorSig = await signCetWithAdaptorSig(
        counterpartyPrivKey,
        signaturePoints[outcomeIndex],
        cet,
        0,
        Buffer.from('')
      );

      const isAdaptorSigValid = await verifyCetAdaptorSig(
        adaptorSig,
        counterpartyPubKey,
        signaturePoints[outcomeIndex],
        cet,
        0,
        Buffer.from('')
      );
      expect(isAdaptorSigValid).to.be.true;

      const oracleSig = await attestEventOutcome(
        oraclePrivKey,
        nonce,
        eventOutcomeHashes[outcomeIndex]
      );
      
      const completedSig = adaptSig(adaptorSig, oracleSig.s);

      const differentCet = createCet(dlcUtxo, 70000, 30000, borrowerAddress, lenderAddress);

      const isValid = await verifyCetSignature(completedSig, counterpartyPubKey, differentCet, 0, Buffer.from(''));
      expect(isValid).to.be.false;
    });
  });

  describe('sigToTaprootBuf', () => {
    it('should correctly serialize a Taproot signature with default sighash', () => {
      // Test vector from BIP341 test cases
      const R = new Point(
        BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983'),
        BigInt('0x78fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533')
      );
      const signature = {
        R,
        s: BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983')
      };

      const serialized = sigToTaprootBuf(signature);
      
      expect(serialized.length).to.equal(64);
      
      expect(Buffer.from(serialized.slice(0, 32)).toString('hex'))
        .to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
      expect(Buffer.from(serialized.slice(32, 64)).toString('hex'))
        .to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
    });

    it('should correctly serialize a Taproot signature with custom sighash', () => {
      const R = new Point(
        BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983'),
        BigInt('0x78fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533')
      );
      const signature = {
        R,
        s: BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983')
      };

      const sighash = 0x01; // SIGHASH_ALL
      const serialized = sigToTaprootBuf(signature, sighash);
      
      expect(serialized.length).to.equal(65);
      expect(Buffer.from(serialized.slice(0, 32)).toString('hex'))
        .to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
      expect(Buffer.from(serialized.slice(32, 64)).toString('hex'))
        .to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
      expect(serialized[64]).to.equal(0x01);
    });
  });
}); 