import { expect } from '@playwright/test';

import { test } from '../assets/jest-helper';
import { protoVoteOp } from "../assets/data.proto-protocol";
import type { IWaxOptionsChain, WaxPrivateKeyLeakDetectedException } from '../../dist/bundle';

import type { IOnlineTransaction, operation, transfer } from '../../dist/bundle';

test.describe('Wax chain tests to cover Online Transaction flow', () => {
  const txSecurityLeakBody = async ({ beekeeper, wax }, mirrornetSkeletonKey: string, config: IWaxOptionsChain, directBroadcast: boolean) => {
    // Create wallet:
    const session = beekeeper.createSession("salt");
    const { wallet } = await session.createWallet("w0");
    const matchingPublicKey = await wallet.importKey(mirrornetSkeletonKey);

    const myCustomChain = await wax.createHiveChain(config);

    // Create online transaction
    const tx: IOnlineTransaction = await myCustomChain.createTransaction();

    // Fill it with some operation
    const transferOp: transfer = {
      from_account: "otom",
      to_account: "otom",
      amount: myCustomChain.hiveCoins(1),
      memo: `${mirrornetSkeletonKey}`
    };

    const op: operation = { transfer: transferOp };
    tx.pushOperation(op);

    try {
      if(directBroadcast)
        await myCustomChain.broadcast(tx);
      else
        await tx.performOnChainVerification();
    }
    catch(e) {
      const error: object = e as object;

      if (e instanceof wax.WaxPrivateKeyLeakDetectedException) {
        const caughtError: WaxPrivateKeyLeakDetectedException = error as WaxPrivateKeyLeakDetectedException;

        return {
          detectedLeakError: {
            account: caughtError.account,
            authorityRole: caughtError.authorityRole,
            matchingPublicKey: caughtError.matchingPublicKey,
            message: caughtError.message
          }
        };
      } else
        throw new Error("Invalid error instance");
    }

    tx.sign(wallet, matchingPublicKey);

    throw new Error("No error detected");
  };

  const mirrornetSkeletonKey = '5JNHfZYKGaomSFvd4NUdQ9qMcEAC43kujbfjueTHpVapX1Kzq2n';
  const mirrornetSkeletonPublicKey = 'STM6LLegbAgLAy28EHrffBVuANFWcFgmqRMW13wBmTExqFE9SCkg4';

  test('Should be able to get authority trace for direct multi-sig from already existing transaction', async ({ waxTest }) => {
    const retVal = await waxTest(async({ wax }) => {
      /// warning: this tx is taken from mainnet - regular chainid must be used, so let's use default initialized chain object
      const chain = await wax.createHiveChain();

      /// tx from https://testexplore.openhive.network/transaction/a7efc7be69861fdcdc39712e532beb8ddc701f03
      const txJson = '{"ref_block_num":808,"ref_block_prefix":1359279161,"extensions":[],"expiration":"2024-08-02T12:09:03","operations":[{"type":"vote_operation","value":{"voter":"ecency","author":"el-panal","weight":100,"permlink":"el-panal-presentacion-de-autores-destacados-dia31072024"}},{"type":"vote_operation","value":{"voter":"ecency.stats","author":"el-panal","weight":100,"permlink":"el-panal-presentacion-de-autores-destacados-dia31072024"}},{"type":"vote_operation","value":{"voter":"esteem.app","author":"el-panal","weight":100,"permlink":"el-panal-presentacion-de-autores-destacados-dia31072024"}},{"type":"vote_operation","value":{"voter":"good-karma","author":"el-panal","weight":100,"permlink":"el-panal-presentacion-de-autores-destacados-dia31072024"}},{"type":"vote_operation","value":{"voter":"esteemapp","author":"el-panal","weight":100,"permlink":"el-panal-presentacion-de-autores-destacados-dia31072024"}}],"signatures":["1f4141e7645dd2bdcdb2001baea165e668a6f9c2a366f6fd2f3e9d878f071f5eb052509eb666b80c1e0daaa7fdec36e3de827087dcd3015c8672b536ddddbc5726","20223edff229382e67031d993c13d26062ab4d33c45ede7aced7f1e432d6b6d57a4fd68eae708f562a9644ee08ea447fa354febe34aa8ba14a434c66adc753774f","20286bd3b0ad2ecb01488ce866361b4fcd53b1f3dac41962496986fdf89c89c20a0e86f8dd2669ed14b6dafc3e0bb0d24ec54a794ee6c0b563275a22a122992ac1","2043b5ea9cbf76f1cd0fbb5f589350d9b8273d241c8bb704189bc9fd4444493c384594536e0387ff121bcdbfa4fa401917e30609ac86ea82f13132d26280e74f9a","205f0e25631bdbcd37669b2f6c36594d0153e468be8b2b6a57f8eae49538132efd0dde1ad6a707788d6a2024efd03a55dd0bb478a40eedce48f6e5db19d365bce5"]}';
      const sourceTx = chain.createTransactionFromJson(txJson);

      // Create online transaction
      const tx = await chain.createTransaction();
      const authTrace = await tx.generateAuthorityVerificationTrace(undefined, sourceTx);

      const authTraceStr = JSON.stringify(authTrace);
      console.log(`Authority trace: ${authTraceStr }`);

      return authTrace.collectedData;
    });

    expect(retVal).toStrictEqual([
      {
        "finalAuthorityPath": {
          "processedEntry": "ecency",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false,
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM8AZuk2ja5vSFySFL2zpB9bNew8wJAg8r4QFtbnoamEX8Jvvq43",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false,
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1,
            },
          ],
          "weight": 1,
        },
        "matchingSignatures": [{
          "signature": "205f0e25631bdbcd37669b2f6c36594d0153e468be8b2b6a57f8eae49538132efd0dde1ad6a707788d6a2024efd03a55dd0bb478a40eedce48f6e5db19d365bce5",
          "signatureKey": "STM8AZuk2ja5vSFySFL2zpB9bNew8wJAg8r4QFtbnoamEX8Jvvq43",
        }],
      },
      {
        "finalAuthorityPath": {
          "processedEntry": "ecency.stats",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false,
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM8jviUDRAefxmTQ9m8wNdiQV5dmCPSMDjSnztPYZpHf1yfaD6Rd",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false,
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1,
            },
          ],
          "weight": 1,
        },
        "matchingSignatures": [{
          "signature": "20223edff229382e67031d993c13d26062ab4d33c45ede7aced7f1e432d6b6d57a4fd68eae708f562a9644ee08ea447fa354febe34aa8ba14a434c66adc753774f",
          "signatureKey": "STM8jviUDRAefxmTQ9m8wNdiQV5dmCPSMDjSnztPYZpHf1yfaD6Rd",
        }],
      },
      {
        "finalAuthorityPath": {
          "processedEntry": "esteem.app",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false,
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM64Bb5TXsiEbjjLsgVrvVttEDsLNSot9p8zJd41D5zEr5opxcHK",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false,
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1,
            },
          ],
          "weight": 1,
        },
        "matchingSignatures": [{
          "signature": "20286bd3b0ad2ecb01488ce866361b4fcd53b1f3dac41962496986fdf89c89c20a0e86f8dd2669ed14b6dafc3e0bb0d24ec54a794ee6c0b563275a22a122992ac1",
          "signatureKey": "STM64Bb5TXsiEbjjLsgVrvVttEDsLNSot9p8zJd41D5zEr5opxcHK",
        }],
      },
      {
        "finalAuthorityPath": {
          "processedEntry": "esteemapp",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false,
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM7S3wsVtQotgKLN8wFLPNBALe6YHt8MPLEHuTH5CxfxdhpGPBUP",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false,
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1,
            },
          ],
          "weight": 1,
        },
        "matchingSignatures": [{
          "signature": "1f4141e7645dd2bdcdb2001baea165e668a6f9c2a366f6fd2f3e9d878f071f5eb052509eb666b80c1e0daaa7fdec36e3de827087dcd3015c8672b536ddddbc5726",
          "signatureKey": "STM7S3wsVtQotgKLN8wFLPNBALe6YHt8MPLEHuTH5CxfxdhpGPBUP",
        }],
      },
      {
        "finalAuthorityPath": {
          "processedEntry": "good-karma",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM5dhkPS223F9d3TCXKttuWpdWgqS2Fx8KNRQve6BMGmAvJ5GnJR",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1
            }
          ],
          "weight": 1
        },
        "matchingSignatures": [{
          "signature": "2043b5ea9cbf76f1cd0fbb5f589350d9b8273d241c8bb704189bc9fd4444493c384594536e0387ff121bcdbfa4fa401917e30609ac86ea82f13132d26280e74f9a",
          "signatureKey": "STM5dhkPS223F9d3TCXKttuWpdWgqS2Fx8KNRQve6BMGmAvJ5GnJR"
        }]
      }
    ]);
  });


  test('Should be able to get authority trace for direct sign', async ({ waxTest, config }) => {
    /// similar tx to https://testexplore.openhive.network/transaction/da9602787693edccdafa1e7325502e0bb14453d1
    const retVal = await waxTest(async ({ beekeeper, wax }, mirrornetSkeletonKey: string, config: IWaxOptionsChain) => {

      const session = beekeeper.createSession("salt");
      const { wallet } = await session.createWallet("w0");
      const matchingPublicKey = await wallet.importKey(mirrornetSkeletonKey);

      const myCustomChain = await wax.createHiveChain(config);

      // Create online transaction
      const tx: IOnlineTransaction = await myCustomChain.createTransaction();

      // Fill it with some operation
      const transferOp: transfer = {
        from_account: "splinterboost",
        to_account: "bluehy20",
        amount: myCustomChain.hiveSatoshis(14),
        memo: 'Thank you for delegating to Splinterboost here is your daily HIVE payout!'
      };

      const op: operation = { transfer: transferOp };
      tx.pushOperation(op);
      tx.sign(wallet, matchingPublicKey);

      const authTrace = await tx.generateAuthorityVerificationTrace();

      authTrace.collectedData[0].matchingSignatures[0].signature = 'fakeSignature'; // The signature changes every time so we need to fake it.

      const authTraceStr = JSON.stringify(authTrace);
      console.log(`Authority trace: ${authTraceStr }`);

      return authTrace.collectedData;
    }, mirrornetSkeletonKey, config!);

    expect(retVal).toStrictEqual([
      {
        "finalAuthorityPath": {
          "processedEntry": "splinterboost",
          "processedRole": "active",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM6LLegbAgLAy28EHrffBVuANFWcFgmqRMW13wBmTExqFE9SCkg4",
              "processedRole": "active",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1
            }
          ],
          "weight": 1
        },
        "matchingSignatures": [{
          "signature": "fakeSignature",
          "signatureKey": "STM6LLegbAgLAy28EHrffBVuANFWcFgmqRMW13wBmTExqFE9SCkg4"
        }]
      }
    ]);
  });

   test('Should be able to get authority trace for direct sign from already existing transaction', async ({ waxTest }) => {
    const retVal = await waxTest(async({wax}) => {
      /// seems this case does not work.
      ///1. trace generator throws instead of producing a trace
      ///2. the transaction is valid, authority should be satisfied

      /// warning: this tx is taken from mainnet - regular chainid must be used, so let's use default initialized chain object
      const chain = await wax.createHiveChain();

      /// tx from https://testexplore.openhive.network/transaction/da9602787693edccdafa1e7325502e0bb14453d1
      const txJson = '{"ref_block_num":33561,"ref_block_prefix":2922397352,"extensions":[],"expiration":"2024-09-20T12:16:45","operations":[{"type":"transfer_operation","value":{"to":"bluehy20","from":"splinterboost","memo":"Thank you for delegating to Splinterboost here is your daily HIVE payout!","amount":{"nai":"@@000000021","amount":"14","precision":3}}}],"signatures":["203eed491a1e032bc889b030861a80bf3f0bc8ba7fa30843041825c4f542411fb3678fd527c598dcc6646a5dabf2110691a4d6355c72d7638ffdaa11ac10451d07"]}';
      const sourceTx = chain.createTransactionFromJson(txJson);

      // Create online transaction
      const tx = await chain.createTransaction();
      const authTrace = await tx.generateAuthorityVerificationTrace(true, sourceTx);

      const authTraceStr = JSON.stringify(authTrace);
      console.log(`Authority trace: ${authTraceStr }`);

      return authTrace.collectedData;
    });

    expect(retVal).toStrictEqual([
      {
        "finalAuthorityPath": {
          "processedEntry": "splinterboost",
          "processedRole": "active",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "STM7jDAdjyLYgqhyCwSafVzNGN4PLBGWrYB9uJun4AitZA8TERgif",
              "processedRole": "active",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false
              },
              "recursionDepth": 0,
              "threshold": 1,
              "visitedEntries": [],
              "weight": 1
            }
          ],
          "weight": 1
        },
        "matchingSignatures": [{
          "signature": "203eed491a1e032bc889b030861a80bf3f0bc8ba7fa30843041825c4f542411fb3678fd527c598dcc6646a5dabf2110691a4d6355c72d7638ffdaa11ac10451d07",
          "signatureKey": "STM7jDAdjyLYgqhyCwSafVzNGN4PLBGWrYB9uJun4AitZA8TERgif"
        }]
      }
    ]);
  });

  test('Should be able to get authority trace for delegated sign', async ({ waxTest }) => {
    const retVal = await waxTest(async({ wax }) => {
      const chain = await wax.createHiveChain();

      const sourceTx = chain.createTransactionFromJson({
        "ref_block_num": 31682,
        "ref_block_prefix": 1691585842,
        "extensions": [],
        "expiration": "2024-10-01T20:10:59",
        "operations": [
          {
            "type": "vote_operation",
            "value": {
              "voter": "tattooworld",
              "author": "mamaemigrante",
              "weight": 10000,
              "permlink": "buscando-ollas-nuevas-para-mi-cocina-looking-for-new-pots-and-pans-for-my-kitchen"
            }
          }
        ],
        "signatures": [
          "20543c6e9e5ea2acfb94e9c5cd6672f302d067b62a4c71832dcaec7caf5e83a83b45ae76c55e3f51f8eb254b460a0585e7f911a93d6e5a58522429b7a4678dc22e"
        ]
      });

      const tx = await chain.createTransaction();

      const trace = await tx.generateAuthorityVerificationTrace(false, sourceTx);

      return trace.collectedData;
    });

    expect(retVal).toStrictEqual([
      {
        "finalAuthorityPath": {
          "processedEntry": "tattooworld",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "leofinance",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false
              },
              "recursionDepth": 1,
              "threshold": 1,
              "visitedEntries": [
                {
                  "processedEntry": "steemauto",
                  "processedRole": "posting",
                  "processingStatus": {
                    "entryAccepted": true,
                    "isOpenAuthority": false
                  },
                  "recursionDepth": 2,
                  "threshold": 1,
                  "visitedEntries": [
                    {
                      "processedEntry": "STM8WWUYHMdHLgEHidYCztswzfZCViA16EqGkAxt7RG4dWwDpFtCF",
                      "processedRole": "posting",
                      "processingStatus": {
                        "entryAccepted": true,
                        "isOpenAuthority": false
                      },
                      "recursionDepth": 2,
                      "threshold": 1,
                      "visitedEntries": [],
                      "weight": 1
                    }
                  ],
                  "weight": 1
                }
              ],
              "weight": 1
            }
          ],
          "weight": 1
        },
        "matchingSignatures": [{
          "signature": "20543c6e9e5ea2acfb94e9c5cd6672f302d067b62a4c71832dcaec7caf5e83a83b45ae76c55e3f51f8eb254b460a0585e7f911a93d6e5a58522429b7a4678dc22e",
          "signatureKey": "STM8WWUYHMdHLgEHidYCztswzfZCViA16EqGkAxt7RG4dWwDpFtCF"
        }]
      }
    ]);
  });

  test('Should be able to get authority trace for delegated sign with single nest level', async ({ waxTest }) => {
    const retVal = await waxTest(async({ wax }) => {
      const chain = await wax.createHiveChain();

      const sourceTx = chain.createTransactionFromJson({
        "ref_block_num": 59824,
        "ref_block_prefix": 3761625792,
        "extensions": [],
        "expiration": "2024-12-12T12:30:00",
        "operations": [
          {
            "type": "vote_operation",
            "value": {
              "voter": "sunnyvo",
              "author": "franciscomarval",
              "weight": 475,
              "permlink": "alegoria-sirenida-mermaid-allegory"
            }
          }
        ],
        "signatures": [
          "20282d87e22cad745d263ee43fe8552044ecb68ebd274a03421d6e59aaaa891d5a594808c58605828c240b9e498f53d32a8f4f7baec5bfcbc7d391af4e4283366e"
        ]
      });

      const tx = await chain.createTransaction();

      const trace = await tx.generateAuthorityVerificationTrace(false, sourceTx);

      return trace.collectedData;
    });

    expect(retVal).toStrictEqual([
      {
        "finalAuthorityPath":       {
          "processedEntry": "sunnyvo",
          "processedRole": "posting",
          "processingStatus": {
            "entryAccepted": true,
            "isOpenAuthority": false
          },
          "recursionDepth": 0,
          "threshold": 1,
          "visitedEntries": [
            {
              "processedEntry": "steemauto",
              "processedRole": "posting",
              "processingStatus": {
                "entryAccepted": true,
                "isOpenAuthority": false
              },
              "recursionDepth": 1,
              "threshold": 1,
              "visitedEntries": [
                {
                  "processedEntry": "STM8WWUYHMdHLgEHidYCztswzfZCViA16EqGkAxt7RG4dWwDpFtCF",
                  "processedRole": "posting",
                  "processingStatus": {
                    "entryAccepted": true,
                    "isOpenAuthority": false
                  },
                  "recursionDepth": 1,
                  "threshold": 1,
                  "visitedEntries": [],
                  "weight": 1
                }
              ],
              "weight": 1
            }
          ],
          "weight": 1
        },
        "matchingSignatures": [{
          "signature": "20282d87e22cad745d263ee43fe8552044ecb68ebd274a03421d6e59aaaa891d5a594808c58605828c240b9e498f53d32a8f4f7baec5bfcbc7d391af4e4283366e",
          "signatureKey": "STM8WWUYHMdHLgEHidYCztswzfZCViA16EqGkAxt7RG4dWwDpFtCF"
        }]
      }
    ]);
  });

  test('Should be able to get authority trace for open authority transaction', async ({ waxTest }) => {
    // The authority trace is not generated correctly for open authority transactions.
    const retVal = await waxTest(async({ wax }) => {
      const chain = await wax.createHiveChain();

      const sourceTx = chain.createTransactionFromJson({
        "ref_block_num": 35292,
        "ref_block_prefix": 2546881088,
        "extensions": [],
        "expiration": "2024-07-27T20:43:36",
        "operations": [
          {
            "type": "comment_operation",
            "value": {
              "body": "With no response, we have no recourse but to release the source code to exploit this will be publicly released on Sunday July 28, 2024.",
              "title": "",
              "author": "temp",
              "permlink": "37",
              "json_metadata": "",
              "parent_author": "hive-engine",
              "parent_permlink": "market-smart-contract-23"
            }
          }
        ],
        "signatures": []
      });

      const tx = await chain.createTransaction();

      const trace = await tx.generateAuthorityVerificationTrace(false, sourceTx);

      console.log(JSON.stringify(trace));

      return trace.collectedData;
    });

    expect(retVal).toStrictEqual([
      {
        "finalAuthorityPath":{
          "processedEntry":"temp",
          "processedRole":"posting",
          "threshold":0,
          "weight":0,
          "recursionDepth":0,
          "processingStatus":{
            "entryAccepted": true,
            "isOpenAuthority": true
          },
          "visitedEntries":[]
        },
        "matchingSignatures": []
      }
    ]);
  });

  test('Should catch private key leak using online transaction interface during explicit online validation', async ({ waxTest, config }) => {
      const retVal = await waxTest(txSecurityLeakBody, mirrornetSkeletonKey, config!, false);
    expect(retVal.detectedLeakError).toStrictEqual({
      account: "otom",
      authorityRole: "owner",
      matchingPublicKey: mirrornetSkeletonPublicKey,
      message: "Detected private key leak."
    });
  });

  test('Should catch private key leak using online transaction interface during direct broadcast', async ({ waxTest, config }) => {
    const retVal = await waxTest(txSecurityLeakBody, mirrornetSkeletonKey, config!, true);
  expect(retVal.detectedLeakError).toStrictEqual({
    account: "otom",
    authorityRole: "owner",
    matchingPublicKey: mirrornetSkeletonPublicKey,
    message: "Detected private key leak."
  });
});

  test('Should be able to create and sign transaction using online transaction interface', async ({ waxTest, config }) => {
    const retVal = await waxTest(async({ beekeeper, wax }, protoVoteOp, mirrornetSkeletonKey, mirrornetSkeletonPublicKey, config) => {
      // Create wallet:
      const session = beekeeper.createSession("salt");
      const { wallet } = await session.createWallet("w0");
      const matchingPublicKey = await wallet.importKey(mirrornetSkeletonKey);

      console.log(`${matchingPublicKey}`);

      const myCustomChain = await wax.createHiveChain(config);

      // Create online transaction
      const tx: IOnlineTransaction = await myCustomChain.createTransaction();

      // Fill it with some operation
      tx.pushOperation(protoVoteOp).validate();

      await tx.performOnChainVerification();

      tx.sign(wallet, matchingPublicKey);

      return {
        signerKey: tx.signatureKeys[0],
        expectedKey: mirrornetSkeletonPublicKey
      };
    }, protoVoteOp, mirrornetSkeletonKey, mirrornetSkeletonPublicKey, config);

    expect(retVal.signerKey).toBe(retVal.expectedKey);
   });
});
