#include "imports/stdlib.fc";
#include "common/errors.fc";
#include "common/utils.fc";
#include "common/storage.fc";
#include "common/constants.fc";

;; Signature verification function
;; ECRECOVER: Recovers the signer's address from the signature
;; It returns 1 value (0) on failure and 4 values on success
;; NULLSWAPIFNOT and NULLSWAPIFNOT2: Ensure consistent return of 4 values
;; These opcodes swap nulls onto the stack if ECRECOVER fails, maintaining the 4-value return
(int, int, int, int) check_sig (int hash, int v, int r, int s) asm
    "ECRECOVER"     ;; Attempt to recover the signer's address
    "NULLSWAPIFNOT" ;; If recovery failed, insert null under the top of the stack
    "NULLSWAPIFNOT2";  ;; If recovery failed, insert two more nulls under the top of the stack

;; Internal helper methods
(int, cell, int) parse_guardian_set(slice guardian_set) {
    slice cs = guardian_set~load_ref().begin_parse();
    int expiration_time = cs~load_uint(64);
    cell keys_dict = cs~load_dict();
    int key_count = 0;
    int key = -1;
    do {
        (key, slice address, int found) = keys_dict.udict_get_next?(8, key);
        if (found) {
            key_count += 1;
        }
    } until (~ found);
    return (expiration_time, keys_dict, key_count);
}

(int, cell, int) get_guardian_set_internal(int index) {
    (slice guardian_set, int found?) = guardian_sets.udict_get?(32, index);
    throw_unless(ERROR_GUARDIAN_SET_NOT_FOUND, found?);
    (int expiration_time, cell keys_dict, int key_count) = parse_guardian_set(guardian_set);
    return (expiration_time, keys_dict, key_count);
}


;; Get methods
int get_chain_id() method_id {
    load_data();
    return chain_id;
}

int get_current_guardian_set_index() method_id {
    load_data();
    return current_guardian_set_index;
}

(int, cell, int) get_guardian_set(int index) method_id {
    load_data();
    return get_guardian_set_internal(index);
}

int get_governance_chain_id() method_id {
    load_data();
    return governance_chain_id;
}

int get_governance_contract() method_id {
    load_data();
    return governance_contract;
}

int governance_action_is_consumed(int hash) method_id {
    load_data();
    (_, int found?) = consumed_governance_actions.udict_get?(256, hash);
    return found?;
}

() verify_signatures(int hash, cell signatures, int signers_length, cell guardian_set_keys, int guardian_set_size) impure {
    slice cs = signatures.begin_parse();
    int i = 0;
    int last_signature_index = -1;
    int valid_signatures = 0;

    while (i < signers_length) {
        int bits_to_load = 528;
        builder sig_builder = begin_cell();

        while (bits_to_load > 0) {
            int available_bits = cs.slice_bits();
            int bits = min(bits_to_load, available_bits);
            sig_builder = sig_builder.store_slice(cs~load_bits(bits));
            bits_to_load -= bits;

            if (bits_to_load > 0) {
                cs = cs~load_ref().begin_parse();
            }
        }

        slice sig_slice = sig_builder.end_cell().begin_parse();
        int guardian_index = sig_slice~load_uint(8);
        throw_if(ERROR_SIGNATURE_INDEX_NOT_INCREASING, guardian_index <= last_signature_index);
        int r = sig_slice~load_uint(256);
        int s = sig_slice~load_uint(256);
        int v = sig_slice~load_uint(8);
        (_, int x1, int x2, int valid) = check_sig(hash, v >= 27 ? v - 27 : v, r, s);
        throw_unless(ERROR_INVALID_SIGNATURES, valid);
        int parsed_address = pubkey_to_eth_address(x1, x2);
        (slice guardian_key, int found?) = guardian_set_keys.udict_get?(8, guardian_index);
        int guardian_address = guardian_key~load_uint(160);
        throw_unless(ERROR_INVALID_GUARDIAN_ADDRESS, parsed_address == guardian_address);
        valid_signatures += 1;
        last_signature_index = guardian_index;
        i += 1;
    }

    ;; Check quorum (2/3 + 1)
    ;; We're using a fixed point number transformation with 1 decimal to deal with rounding.
    throw_unless(ERROR_NO_QUORUM, valid_signatures >= (((guardian_set_size * 10) / 3) * 2) / 10 + 1);
}

(int, int, int, int, int, int, int, int, slice, int) parse_and_verify_wormhole_vm(slice in_msg_body) impure {
    load_data();
    ;; Parse VM fields
    int version = in_msg_body~load_uint(8);
    throw_unless(ERROR_INVALID_VERSION, version == WORMHOLE_VM_VERSION);
    int vm_guardian_set_index = in_msg_body~load_uint(32);
    ;; Verify and check if guardian set is valid
    (int expiration_time, cell keys_dict, int key_count) = get_guardian_set_internal(vm_guardian_set_index);
    throw_if(ERROR_INVALID_GUARDIAN_SET_KEYS_LENGTH, cell_null?(keys_dict));
    throw_unless(ERROR_INVALID_GUARDIAN_SET,
        (current_guardian_set_index >= vm_guardian_set_index) &
        ((expiration_time == 0) | (expiration_time > now()))
    );
    int signers_length = in_msg_body~load_uint(8);
    ;; Calculate signatures_size in bits (66 bytes per signature: 1 (guardianIndex) + 32 (r) + 32 (s) + 1 (v))
    int signatures_size = signers_length * 66 * 8;

    ;; Load signatures
    (cell signatures, slice remaining_body) = read_and_store_large_data(in_msg_body, signatures_size);
    in_msg_body = remaining_body;

    ;; Calculate total body length across all references
    int body_length = 0;
    int continue? = true;
    do {
        body_length += remaining_body.slice_bits();
        if (remaining_body.slice_refs_empty?()) {
            continue? = false;
        } else {
            remaining_body = remaining_body~load_ref().begin_parse();
        }
    } until (~ continue?);

    ;; Load body
    (cell body_cell, _) = read_and_store_large_data(in_msg_body, body_length);

    int hash = keccak256_int(keccak256_slice(body_cell.begin_parse()));
    ;; Verify signatures
    verify_signatures(hash, signatures, signers_length, keys_dict, key_count);

    slice body_slice = body_cell.begin_parse();
    int timestamp = body_slice~load_uint(32);
    int nonce = body_slice~load_uint(32);
    int emitter_chain_id = body_slice~load_uint(16);
    int emitter_address = body_slice~load_uint(256);
    int sequence = body_slice~load_uint(64);
    int consistency_level = body_slice~load_uint(8);
    slice payload = body_slice;

    return (
        version,
        vm_guardian_set_index,
        timestamp,
        nonce,
        emitter_chain_id,
        emitter_address,
        sequence,
        consistency_level,
        payload,
        hash
    );
}

(int, int, int, cell, int) parse_encoded_upgrade(int guardian_set_index, slice payload) impure {
    int module = payload~load_uint(256);
    throw_unless(ERROR_INVALID_MODULE, module == UPGRADE_MODULE);

    int action = payload~load_uint(8);
    throw_unless(ERROR_INVALID_GOVERNANCE_ACTION, action == GUARDIAN_SET_UPGRADE_ACTION);

    int chain = payload~load_uint(16);
    int new_guardian_set_index = payload~load_uint(32);
    throw_unless(ERROR_NEW_GUARDIAN_SET_INDEX_IS_INVALID, new_guardian_set_index == (guardian_set_index + 1));

    int guardian_length = payload~load_uint(8);
    throw_unless(ERROR_INVALID_GUARDIAN_SET_KEYS_LENGTH, guardian_length > 0);

    cell new_guardian_set_keys = new_dict();
    int key_count = 0;
    while (key_count < guardian_length) {
        builder key = begin_cell();
        int key_bits_loaded = 0;
        while (key_bits_loaded < 160) {
            int bits_to_load = min(payload.slice_bits(), 160 - key_bits_loaded);
            key = key.store_slice(payload~load_bits(bits_to_load));
            key_bits_loaded += bits_to_load;
            if (key_bits_loaded < 160) {
                throw_unless(ERROR_INVALID_GUARDIAN_SET_UPGRADE_LENGTH, ~ payload.slice_refs_empty?());
                payload = payload~load_ref().begin_parse();
            }
        }
        slice key_slice = key.end_cell().begin_parse();
        new_guardian_set_keys~udict_set(8, key_count, key_slice);
        key_count += 1;
    }
    throw_unless(ERROR_GUARDIAN_SET_KEYS_LENGTH_NOT_EQUAL, key_count == guardian_length);
    throw_unless(ERROR_INVALID_GUARDIAN_SET_UPGRADE_LENGTH, payload.slice_empty?());

    return (action, chain, module, new_guardian_set_keys, new_guardian_set_index);
}

() update_guardian_set(slice in_msg_body) impure {
    ;; Verify governance VM
    (int version, int vm_guardian_set_index, int timestamp, int nonce, int emitter_chain_id, int emitter_address, int sequence, int consistency_level, slice payload, int hash) = parse_and_verify_wormhole_vm(in_msg_body);

    ;; Verify the emitter chain and address
    throw_unless(ERROR_INVALID_GOVERNANCE_CHAIN, emitter_chain_id == governance_chain_id);
    throw_unless(ERROR_INVALID_GOVERNANCE_CONTRACT, emitter_address == governance_contract);

    ;; Check if the governance action has already been consumed
    throw_if(ERROR_GOVERNANCE_ACTION_ALREADY_CONSUMED, governance_action_is_consumed(hash));

    ;; Parse the new guardian set from the payload
    (int action, int chain, int module, cell new_guardian_set_keys, int new_guardian_set_index) = parse_encoded_upgrade(current_guardian_set_index, payload);

    ;; Set expiry if current GuardianSet exists
    (slice current_guardian_set, int found?) = guardian_sets.udict_get?(32, current_guardian_set_index);
    if (found?) {
        (int expiration_time, cell keys_dict, int key_count) = parse_guardian_set(current_guardian_set);
        cell updated_guardian_set = begin_cell()
            .store_uint(now() + GUARDIAN_SET_EXPIRY, 64) ;; expiration time
            .store_dict(keys_dict) ;; keys
        .end_cell();
        guardian_sets~udict_set(32, current_guardian_set_index, begin_cell().store_ref(updated_guardian_set).end_cell().begin_parse()); ;; store reference to updated_guardian_set because storeDict stores a dict into a ref
    }

    ;; Store the new guardian set
    cell new_guardian_set = begin_cell()
        .store_uint(0, 64) ;; expiration_time, set to 0 initially
        .store_dict(new_guardian_set_keys)
    .end_cell();
    guardian_sets~udict_set(32, new_guardian_set_index, begin_cell().store_ref(new_guardian_set).end_cell().begin_parse());

    ;; Update the current guardian set index
    current_guardian_set_index = new_guardian_set_index;

    ;; Mark the governance action as consumed
    consumed_governance_actions~udict_set(256, hash, begin_cell().store_int(true, 1).end_cell().begin_parse());
    store_data();
}
