# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Apache 2.0 License.
import infra.e2e_args
import infra.ccf
import infra.jsonrpc

import logging
from time import gmtime, strftime
import csv
import random

from loguru import logger as LOG


class AppUser:
    def __init__(self, network, name, country, curve):
        self.name = name
        self.country = country

        primary, _ = network.find_primary()

        network.create_users([self.name], curve)
        network.consortium.add_users(primary, [self.name])

        with primary.user_client(user_id=self.name) as client:
            self.ccf_id = client.rpc("whoAmI", {}).result["caller_id"]

    def __str__(self):
        return f"{self.ccf_id} ({self.name})"


def run(args):
    hosts = ["localhost"]

    with infra.ccf.network(
        hosts, args.build_dir, args.debug_nodes, args.perf_nodes, pdb=args.pdb
    ) as network:
        check = infra.checker.Checker()
        network.start_and_join(args)
        primary, others = network.find_nodes()

        script = "if tonumber(amt) > 200000 then return true else return false end"
        if args.lua_script is not None:
            data = []
            with open(args.lua_script, "r") as f:
                data = f.readlines()
            script = "".join(data)

        manager = AppUser(network, "manager", "GB", args.default_curve)
        regulator = AppUser(network, "auditor", "GB", args.default_curve)
        banks = [
            AppUser(network, f"bank{country}", country, args.default_curve)
            for country in ("US", "GB", "GR", "FR")
        ]
        transactions = []

        with open(args.datafile, newline="") as f:
            datafile = csv.DictReader(f)
            for i, row in enumerate(datafile):
                # read first 10 lines
                if i > 10:
                    break
                json_tx = {
                    "src": row["origin"],
                    "dst": row["destination"],
                    "amt": row["amount"],
                    "type": row["type"],
                    "timestamp": strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()),
                    "src_country": row["src_country"],
                    "dst_country": row["dst_country"],
                }
                transactions.append(json_tx)

        # Manager is granted special privileges by members, which is later read by app to enforce access restrictions
        proposal_result, error = network.consortium.propose(
            0,
            primary,
            f"""
            return Calls:call(
                "set_user_data",
                {{
                    user_id = {manager.ccf_id},
                    user_data = {{
                        privileges = {{
                            REGISTER_REGULATORS = true,
                            REGISTER_BANKS = true,
                        }}
                    }}
                }}
            )
            """,
        )
        network.consortium.vote_using_majority(primary, proposal_result["id"])

        # Check permissions are enforced
        with primary.user_client(user_id=regulator.name) as c:
            check(
                c.rpc("REG_register", {}),
                error=lambda e: e is not None
                and e["code"] == infra.jsonrpc.ErrorCode.INVALID_CALLER_ID.value,
            )
            check(
                c.rpc("BK_register", {}),
                error=lambda e: e is not None
                and e["code"] == infra.jsonrpc.ErrorCode.INVALID_CALLER_ID.value,
            )

        with primary.user_client(user_id=banks[0].name) as c:
            check(
                c.rpc("REG_register", {}),
                error=lambda e: e is not None
                and e["code"] == infra.jsonrpc.ErrorCode.INVALID_CALLER_ID.value,
            )
            check(
                c.rpc("BK_register", {}),
                error=lambda e: e is not None
                and e["code"] == infra.jsonrpc.ErrorCode.INVALID_CALLER_ID.value,
            )

        # As permissioned manager, register regulator and banks
        with primary.node_client() as mc:
            check_commit = infra.checker.Checker(mc)

            with primary.user_client(format="msgpack", user_id=manager.name) as c:
                check(
                    c.rpc(
                        "REG_register",
                        {
                            "regulator_id": regulator.ccf_id,
                            "country": regulator.country,
                            "script": script,
                        },
                    ),
                    result=regulator.ccf_id,
                )
                check(
                    c.rpc("REG_get", {"id": regulator.ccf_id}),
                    result=[regulator.country, script],
                )

                check(
                    c.rpc(
                        "BK_register",
                        {"bank_id": regulator.ccf_id, "country": regulator.country},
                    ),
                    error=lambda e: e is not None
                    and e["code"] == infra.jsonrpc.ErrorCode.INVALID_PARAMS.value,
                )
                LOG.debug(f"User {regulator} successfully registered as regulator")

                for bank in banks:
                    check(
                        c.rpc(
                            "BK_register",
                            {"bank_id": bank.ccf_id, "country": bank.country},
                        ),
                        result=bank.ccf_id,
                    )
                    check(c.rpc("BK_get", {"id": bank.ccf_id}), result=bank.country)

                    check(
                        c.rpc(
                            "REG_register",
                            {"regulator_id": bank.ccf_id, "country": bank.country},
                        ),
                        error=lambda e: e is not None
                        and e["code"] == infra.jsonrpc.ErrorCode.INVALID_PARAMS.value,
                    )
                    LOG.debug(f"User {bank} successfully registered as bank")

        LOG.success(f"{1} regulator and {len(banks)} bank(s) successfully setup")

        tx_id = 0  # Tracks how many transactions have been issued
        # tracks flagged/non flagged and revealed/non revealed transactions for validation
        flagged_txs = {}
        revealed_tx_ids = []
        flagged_ids = []
        non_flagged_ids = []
        flagged_amt = 200000

        for i, bank in enumerate(banks):
            with primary.user_client(format="msgpack", user_id=bank.name) as c:
                # Destination account is the next one in the list of banks
                for transaction in transactions:
                    print(transaction)
                    amount = transaction["amt"]

                    check(c.rpc("TX_record", transaction), result=tx_id)
                    check(
                        c.rpc("TX_get", {"tx_id": tx_id}),
                        result={
                            "amt": amount,
                            "bank_id": bank.ccf_id,
                            "dst": transaction["dst"],
                            "dst_country": transaction["dst_country"],
                            "src": transaction["src"],
                            "src_country": transaction["src_country"],
                            "timestamp": transaction["timestamp"],
                            "type": transaction["type"],
                        },
                    )
                    if float(amount) > flagged_amt:
                        check(
                            c.rpc("FLAGGED_TX_get", {"tx_id": tx_id}),
                            result=[regulator.ccf_id, False, transaction["timestamp"]],
                        )
                        flagged_tx = {
                            "amt": amount,
                            "bank_id": bank.ccf_id,
                            "dst": transaction["dst"],
                            "dst_country": transaction["dst_country"],
                            "src": transaction["src"],
                            "src_country": transaction["src_country"],
                            "timestamp": transaction["timestamp"],
                            "tx_id": tx_id,
                            "type": transaction["type"],
                        }
                        flagged_ids.append(tx_id)
                        flagged_txs[tx_id] = flagged_tx
                    else:
                        check(
                            c.rpc("FLAGGED_TX_get", {"tx_id": tx_id}),
                            error=lambda e: e is not None
                            and e["code"]
                            == infra.jsonrpc.ErrorCode.INVALID_PARAMS.value,
                        )
                        non_flagged_ids.append(tx_id)

                    tx_id += 1
        LOG.success(f"{tx_id} transactions have been successfully issued")

        # bank that issued first flagged transaction
        with primary.user_client(format="msgpack", user_id=bank.name) as c:
            # try to poll flagged but fail as you are not a regulator
            check(
                c.rpc("REG_poll_flagged", {}),
                error=lambda e: e is not None
                and e["code"] == infra.jsonrpc.ErrorCode.INVALID_CALLER_ID.value,
            )

            # bank reveal some transactions that were flagged
            for i, tx_id in enumerate(flagged_ids):
                if i % 2 == 0:
                    check(c.rpc("TX_reveal", {"tx_id": tx_id}), result=True)
                    revealed_tx_ids.append(tx_id)

            # bank try to reveal non flagged txs
            for tx_id in non_flagged_ids:
                check(
                    c.rpc("TX_reveal", {"tx_id": tx_id}),
                    error=lambda e: e is not None
                    and e["code"] == infra.jsonrpc.ErrorCode.INVALID_PARAMS.value,
                )

        # regulator poll for transactions that are flagged
        with primary.node_client() as mc:
            with primary.user_client(format="msgpack", user_id=regulator.name) as c:
                # assert that the flagged txs that we poll for are correct
                resp = c.rpc("REG_poll_flagged", {})
                poll_flagged_ids = []
                for poll_flagged in resp.result:
                    # poll flagged is a list [tx_id, regulator_id]
                    poll_flagged_ids.append(poll_flagged[0])
                poll_flagged_ids.sort()
                assert poll_flagged_ids == flagged_ids

                for tx_id in flagged_ids:
                    # get from flagged txs, try to get the flagged one that was not revealed
                    if tx_id not in revealed_tx_ids:
                        check(
                            c.rpc("REG_get_revealed", {"tx_id": tx_id}),
                            error=lambda e: e is not None
                            and e["code"]
                            == infra.jsonrpc.ErrorCode.INVALID_PARAMS.value,
                        )

                # get from flagged txs, try to get the flagged ones that were revealed
                for tx_id in revealed_tx_ids:
                    check(
                        c.rpc("REG_get_revealed", {"tx_id": tx_id}),
                        result=flagged_txs[tx_id],
                    )


if __name__ == "__main__":

    def add(parser):
        parser.add_argument(
            "--lua-script", help="Regulator checker loaded as lua script file", type=str
        )
        parser.add_argument(
            "--datafile", help="Load an existing scenario file (csv)", type=str
        )

    args = infra.e2e_args.cli_args(add)
    args.package = args.app_script and "libluageneric" or "liblogging"
    run(args)

[((230, 8, 230, 74), 'loguru.logger.success', 'LOG.success', ({(230, 20, 230, 73): 'f"""{tx_id} transactions have been successfully issued"""'}, {}), "(f'{tx_id} transactions have been successfully issued')", True, 'from loguru import logger as LOG\n'), ((58, 23, 58, 40), 'csv.DictReader', 'csv.DictReader', ({(58, 38, 58, 39): 'f'}, {}), '(f)', False, 'import csv\n'), ((149, 16, 149, 83), 'loguru.logger.debug', 'LOG.debug', ({(149, 26, 149, 82): 'f"""User {regulator} successfully registered as regulator"""'}, {}), "(f'User {regulator} successfully registered as regulator')", True, 'from loguru import logger as LOG\n'), ((169, 20, 169, 77), 'loguru.logger.debug', 'LOG.debug', ({(169, 30, 169, 76): 'f"""User {bank} successfully registered as bank"""'}, {}), "(f'User {bank} successfully registered as bank')", True, 'from loguru import logger as LOG\n'), ((68, 73, 68, 81), 'time.gmtime', 'gmtime', ({}, {}), '()', False, 'from time import gmtime, strftime\n')]