#include "mf_ultralight_poller_i.h"

#include <nfc/protocols/nfc_poller_base.h>

#include <furi.h>
#include <furi_hal.h>

#define TAG "MfUltralightPoller"

typedef NfcCommand (*MfUltralightPollerReadHandler)(MfUltralightPoller* instance);

static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k(
    uint16_t lin_addr,
    uint8_t* sector,
    uint8_t* tag,
    uint8_t* pages_left) {
    bool tag_calculated = false;
    // 0 - 226: sector 0
    // 227 - 228: config registers
    // 229 - 230: session registers

    if(lin_addr > 230) {
        *pages_left = 0;
    } else if(lin_addr >= 229) {
        *sector = 3;
        *pages_left = 2 - (lin_addr - 229);
        *tag = lin_addr - 229 + 248;
        tag_calculated = true;
    } else if(lin_addr >= 227) {
        *sector = 0;
        *pages_left = 2 - (lin_addr - 227);
        *tag = lin_addr - 227 + 232;
        tag_calculated = true;
    } else {
        *sector = 0;
        *pages_left = 227 - lin_addr;
        *tag = lin_addr;
        tag_calculated = true;
    }

    return tag_calculated;
}

static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k(
    uint16_t lin_addr,
    uint8_t* sector,
    uint8_t* tag,
    uint8_t* pages_left) {
    bool tag_calculated = false;
    // 0 - 255: sector 0
    // 256 - 480: sector 1
    // 481 - 482: config registers
    // 483 - 484: session registers

    if(lin_addr > 484) {
        *pages_left = 0;
    } else if(lin_addr >= 483) {
        *sector = 3;
        *pages_left = 2 - (lin_addr - 483);
        *tag = lin_addr - 483 + 248;
        tag_calculated = true;
    } else if(lin_addr >= 481) {
        *sector = 1;
        *pages_left = 2 - (lin_addr - 481);
        *tag = lin_addr - 481 + 232;
        tag_calculated = true;
    } else if(lin_addr >= 256) {
        *sector = 1;
        *pages_left = 225 - (lin_addr - 256);
        *tag = lin_addr - 256;
        tag_calculated = true;
    } else {
        *sector = 0;
        *pages_left = 256 - lin_addr;
        *tag = lin_addr;
        tag_calculated = true;
    }

    return tag_calculated;
}

static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k(
    uint16_t lin_addr,
    uint8_t* sector,
    uint8_t* tag,
    uint8_t* pages_left) {
    bool tag_calculated = false;
    // 0 - 233: sector 0 + registers
    // 234 - 235: session registers

    if(lin_addr > 235) {
        *pages_left = 0;
    } else if(lin_addr >= 234) {
        *sector = 0;
        *pages_left = 2 - (lin_addr - 234);
        *tag = lin_addr - 234 + 236;
        tag_calculated = true;
    } else {
        *sector = 0;
        *pages_left = 234 - lin_addr;
        *tag = lin_addr;
        tag_calculated = true;
    }

    return tag_calculated;
}

static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k(
    uint16_t lin_addr,
    uint8_t* sector,
    uint8_t* tag,
    uint8_t* pages_left) {
    bool tag_calculated = false;
    // 0 - 233: sector 0 + registers
    // 234 - 235: session registers
    // 236 - 491: sector 1

    if(lin_addr > 491) {
        *pages_left = 0;
    } else if(lin_addr >= 236) {
        *sector = 1;
        *pages_left = 256 - (lin_addr - 236);
        *tag = lin_addr - 236;
        tag_calculated = true;
    } else if(lin_addr >= 234) {
        *sector = 0;
        *pages_left = 2 - (lin_addr - 234);
        *tag = lin_addr - 234 + 236;
        tag_calculated = true;
    } else {
        *sector = 0;
        *pages_left = 234 - lin_addr;
        *tag = lin_addr;
        tag_calculated = true;
    }

    return tag_calculated;
}

bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag(
    MfUltralightPoller* instance,
    uint16_t lin_addr,
    uint8_t* sector,
    uint8_t* tag,
    uint8_t* pages_left) {
    furi_assert(instance);
    furi_assert(sector);
    furi_assert(tag);
    furi_assert(pages_left);

    bool tag_calculated = false;

    if(instance->data->type == MfUltralightTypeNTAGI2C1K) {
        tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k(
            lin_addr, sector, tag, pages_left);
    } else if(instance->data->type == MfUltralightTypeNTAGI2C2K) {
        tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k(
            lin_addr, sector, tag, pages_left);
    } else if(instance->data->type == MfUltralightTypeNTAGI2CPlus1K) {
        tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k(
            lin_addr, sector, tag, pages_left);
    } else if(instance->data->type == MfUltralightTypeNTAGI2CPlus2K) {
        tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k(
            lin_addr, sector, tag, pages_left);
    }

    return tag_calculated;
}

MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) {
    furi_assert(iso14443_3a_poller);

    MfUltralightPoller* instance = malloc(sizeof(MfUltralightPoller));
    instance->iso14443_3a_poller = iso14443_3a_poller;
    instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE);
    instance->rx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE);
    instance->data = mf_ultralight_alloc();

    instance->mfu_event.data = &instance->mfu_event_data;

    instance->general_event.protocol = NfcProtocolMfUltralight;
    instance->general_event.event_data = &instance->mfu_event;
    instance->general_event.instance = instance;
    mbedtls_des3_init(&instance->des_context);
    return instance;
}

void mf_ultralight_poller_free(MfUltralightPoller* instance) {
    furi_assert(instance);
    furi_assert(instance->data);
    furi_assert(instance->tx_buffer);
    furi_assert(instance->rx_buffer);

    bit_buffer_free(instance->tx_buffer);
    bit_buffer_free(instance->rx_buffer);
    mf_ultralight_free(instance->data);
    mbedtls_des3_free(&instance->des_context);
    free(instance);
}

static void mf_ultralight_poller_set_callback(
    MfUltralightPoller* instance,
    NfcGenericCallback callback,
    void* context) {
    furi_assert(instance);
    furi_assert(callback);

    instance->callback = callback;
    instance->context = context;
}

const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance) {
    furi_assert(instance);

    return instance->data;
}

static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance) {
    bit_buffer_reset(instance->tx_buffer);
    bit_buffer_reset(instance->rx_buffer);
    iso14443_3a_copy(
        instance->data->iso14443_3a_data,
        iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
    instance->counters_read = 0;
    instance->counters_total = 3;
    instance->tearing_flag_read = 0;
    instance->tearing_flag_total = 3;
    instance->pages_read = 0;
    instance->state = MfUltralightPollerStateRequestMode;
    instance->current_page = 0;
    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_request_mode(MfUltralightPoller* instance) {
    NfcCommand command = NfcCommandContinue;

    instance->mfu_event.type = MfUltralightPollerEventTypeRequestMode;
    instance->mfu_event.data->poller_mode = MfUltralightPollerModeRead;

    command = instance->callback(instance->general_event, instance->context);
    instance->mode = instance->mfu_event.data->poller_mode;

    instance->state = MfUltralightPollerStateReadVersion;
    return command;
}

static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) {
    instance->error = mf_ultralight_poller_read_version(instance, &instance->data->version);
    if(instance->error == MfUltralightErrorNone) {
        FURI_LOG_D(TAG, "Read version success");
        instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version);
        instance->state = MfUltralightPollerStateGetFeatureSet;
    } else {
        FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C");
        iso14443_3a_poller_halt(instance->iso14443_3a_poller);
        instance->state = MfUltralightPollerStateDetectMfulC;
    }

    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) {
    instance->error = mf_ultralight_poller_authentication_test(instance);
    if(instance->error == MfUltralightErrorNone) {
        FURI_LOG_D(TAG, "Ultralight C detected");
        instance->data->type = MfUltralightTypeMfulC;
        instance->state = MfUltralightPollerStateGetFeatureSet;
    } else {
        FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203");
        instance->state = MfUltralightPollerStateDetectNtag203;
    }
    iso14443_3a_poller_halt(instance->iso14443_3a_poller);
    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) {
    MfUltralightPollerState next_state = MfUltralightPollerStateGetFeatureSet;
    MfUltralightPageReadCommandData data = {};
    instance->error = mf_ultralight_poller_read_page(instance, 41, &data);
    if(instance->error == MfUltralightErrorNone) {
        FURI_LOG_D(TAG, "NTAG203 detected");
        instance->data->type = MfUltralightTypeNTAG203;
    } else {
        FURI_LOG_D(TAG, "Original Ultralight detected");
        iso14443_3a_poller_halt(instance->iso14443_3a_poller);
        instance->data->type = MfUltralightTypeOrigin;
    }
    instance->state = next_state;

    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_get_feature_set(MfUltralightPoller* instance) {
    instance->feature_set = mf_ultralight_get_feature_support_set(instance->data->type);
    instance->pages_total = mf_ultralight_get_pages_total(instance->data->type);
    instance->data->pages_total = instance->pages_total;
    FURI_LOG_D(
        TAG,
        "%s detected. Total pages: %d",
        mf_ultralight_get_device_name(instance->data, NfcDeviceNameTypeFull),
        instance->pages_total);

    instance->state = MfUltralightPollerStateReadSignature;
    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller* instance) {
    MfUltralightPollerState next_state = MfUltralightPollerStateAuth;
    if(mf_ultralight_support_feature(
           instance->feature_set, MfUltralightFeatureSupportReadSignature)) {
        FURI_LOG_D(TAG, "Reading signature");
        instance->error =
            mf_ultralight_poller_read_signature(instance, &instance->data->signature);
        if(instance->error != MfUltralightErrorNone) {
            FURI_LOG_D(TAG, "Read signature failed");
            next_state = MfUltralightPollerStateReadFailed;
        }
    } else {
        FURI_LOG_D(TAG, "Skip reading signature");
        if(mf_ultralight_support_feature(
               instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
            next_state = MfUltralightPollerStateAuthMfulC;
        }
    }
    instance->state = next_state;

    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_read_counters(MfUltralightPoller* instance) {
    do {
        if(!mf_ultralight_support_feature(
               instance->feature_set, MfUltralightFeatureSupportReadCounter) ||
           !mf_ultralight_is_counter_configured(instance->data)) {
            FURI_LOG_D(TAG, "Skip reading counters");
            instance->state = MfUltralightPollerStateReadTearingFlags;
            break;
        }

        MfUltralightConfigPages* config = NULL;
        mf_ultralight_get_config_page(instance->data, &config);

        if(config->access.nfc_cnt_pwd_prot && !instance->auth_context.auth_success) {
            FURI_LOG_D(TAG, "Counter reading is protected with password");
            instance->state = MfUltralightPollerStateReadTearingFlags;
            break;
        }

        if(instance->counters_read == instance->counters_total) {
            instance->state = MfUltralightPollerStateReadTearingFlags;
            break;
        }

        if(mf_ultralight_support_feature(
               instance->feature_set, MfUltralightFeatureSupportSingleCounter)) {
            instance->counters_read = 2;
        }

        FURI_LOG_D(TAG, "Reading counter %d", instance->counters_read);
        instance->error = mf_ultralight_poller_read_counter(
            instance, instance->counters_read, &instance->data->counter[instance->counters_read]);
        if(instance->error != MfUltralightErrorNone) {
            FURI_LOG_D(TAG, "Failed to read %d counter", instance->counters_read);
            instance->state = MfUltralightPollerStateReadTearingFlags;
        } else {
            instance->counters_read++;
        }

    } while(false);

    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_read_tearing_flags(MfUltralightPoller* instance) {
    NfcCommand command = NfcCommandContinue;

    if(mf_ultralight_support_feature(
           instance->feature_set,
           MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportSingleCounter)) {
        if(instance->tearing_flag_read == instance->tearing_flag_total) {
            instance->state = MfUltralightPollerStateTryDefaultPass;
            command = NfcCommandReset;
        } else {
            bool single_counter = mf_ultralight_support_feature(
                instance->feature_set, MfUltralightFeatureSupportSingleCounter);
            if(single_counter) instance->tearing_flag_read = 2;

            FURI_LOG_D(TAG, "Reading tearing flag %d", instance->tearing_flag_read);
            instance->error = mf_ultralight_poller_read_tearing_flag(
                instance,
                instance->tearing_flag_read,
                &instance->data->tearing_flag[instance->tearing_flag_read]);
            if((instance->error == MfUltralightErrorProtocol) && single_counter) {
                instance->tearing_flag_read++;
            } else if(instance->error != MfUltralightErrorNone) {
                FURI_LOG_D(TAG, "Reading tearing flag %d failed", instance->tearing_flag_read);
                instance->state = MfUltralightPollerStateTryDefaultPass;
                command = NfcCommandReset;
            } else {
                instance->tearing_flag_read++;
            }
        }
    } else {
        FURI_LOG_D(TAG, "Skip reading tearing flags");
        instance->state = MfUltralightPollerStateTryDefaultPass;
        command = NfcCommandReset;
    }

    return command;
}

static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance) {
    NfcCommand command = NfcCommandContinue;
    if(mf_ultralight_support_feature(
           instance->feature_set, MfUltralightFeatureSupportPasswordAuth)) {
        instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest;

        command = instance->callback(instance->general_event, instance->context);
        if(!instance->mfu_event.data->auth_context.skip_auth) {
            instance->auth_context.password = instance->mfu_event.data->auth_context.password;
            uint32_t pass = bit_lib_bytes_to_num_be(
                instance->auth_context.password.data, sizeof(MfUltralightAuthPassword));
            FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass);
            instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context);
            if(instance->error == MfUltralightErrorNone) {
                FURI_LOG_D(TAG, "Auth success");
                instance->auth_context.auth_success = true;
                instance->mfu_event.data->auth_context = instance->auth_context;
                instance->mfu_event.type = MfUltralightPollerEventTypeAuthSuccess;
                command = instance->callback(instance->general_event, instance->context);
            } else {
                FURI_LOG_D(TAG, "Auth failed");
                instance->auth_context.auth_success = false;
                instance->mfu_event.type = MfUltralightPollerEventTypeAuthFailed;
                command = instance->callback(instance->general_event, instance->context);
                iso14443_3a_poller_halt(instance->iso14443_3a_poller);
            }
        }
    }
    instance->state = MfUltralightPollerStateReadPages;

    return command;
}

static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPoller* instance) {
    NfcCommand command = NfcCommandContinue;
    FURI_LOG_D(TAG, "MfulC auth");
    if(mf_ultralight_support_feature(
           instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
        instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest;

        command = instance->callback(instance->general_event, instance->context);
        if(!instance->mfu_event.data->auth_context.skip_auth) {
            FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
            // Only use the key if it was actually provided
            if(instance->mfu_event.data->key_request_data.key_provided) {
                instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
            } else if(instance->mode == MfUltralightPollerModeDictAttack) {
                // TODO:  -nofl Can logic be rearranged to request this key
                // before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
                FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary");
                // Trigger dictionary key request
                instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
                command = instance->callback(instance->general_event, instance->context);
                if(!instance->mfu_event.data->key_request_data.key_provided) {
                    instance->state = MfUltralightPollerStateReadPages;
                    return command;
                } else {
                    instance->auth_context.tdes_key =
                        instance->mfu_event.data->key_request_data.key;
                }
            } else {
                // Fallback: use key from auth context (for sync poller compatibility)
                instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
            }
            instance->auth_context.auth_success = false;
            // For debugging
            FURI_LOG_D(
                "TAG",
                "Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                instance->auth_context.tdes_key.data[0],
                instance->auth_context.tdes_key.data[1],
                instance->auth_context.tdes_key.data[2],
                instance->auth_context.tdes_key.data[3],
                instance->auth_context.tdes_key.data[4],
                instance->auth_context.tdes_key.data[5],
                instance->auth_context.tdes_key.data[6],
                instance->auth_context.tdes_key.data[7],
                instance->auth_context.tdes_key.data[8],
                instance->auth_context.tdes_key.data[9],
                instance->auth_context.tdes_key.data[10],
                instance->auth_context.tdes_key.data[11],
                instance->auth_context.tdes_key.data[12],
                instance->auth_context.tdes_key.data[13],
                instance->auth_context.tdes_key.data[14],
                instance->auth_context.tdes_key.data[15]);
            do {
                uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
                uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
                furi_hal_random_fill_buf(RndA, sizeof(RndA));
                instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output);
                if(instance->error != MfUltralightErrorNone) break;

                uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
                const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
                instance->error = mf_ultralight_poller_authenticate_end(
                    instance, RndB, output, decoded_shifted_RndA);
                if(instance->error != MfUltralightErrorNone) break;

                mf_ultralight_3des_shift_data(RndA);
                instance->auth_context.auth_success =
                    (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
                if(instance->auth_context.auth_success) {
                    FURI_LOG_E(TAG, "Auth success");
                    if(instance->mode == MfUltralightPollerModeDictAttack) {
                        memcpy(
                            &instance->data->page[44],
                            instance->auth_context.tdes_key.data,
                            MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
                        // Continue to read pages after successful authentication
                        instance->state = MfUltralightPollerStateReadPages;
                    }
                }
            } while(false);
            if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) {
                FURI_LOG_E(TAG, "Auth failed");
                iso14443_3a_poller_halt(instance->iso14443_3a_poller);
                if(instance->mode == MfUltralightPollerModeDictAttack) {
                    // Not needed? We already do a callback earlier?
                    instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
                    command = instance->callback(instance->general_event, instance->context);
                    if(!instance->mfu_event.data->key_request_data.key_provided) {
                        instance->state = MfUltralightPollerStateReadPages;
                    } else {
                        instance->auth_context.tdes_key =
                            instance->mfu_event.data->key_request_data.key;
                        instance->state = MfUltralightPollerStateAuthMfulC;
                    }
                }
            }
        }
    }
    // Regression review
    if(instance->mode != MfUltralightPollerModeDictAttack) {
        instance->state = MfUltralightPollerStateReadPages;
    }
    return command;
}

static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* instance) {
    MfUltralightPageReadCommandData data = {};
    uint16_t start_page = instance->pages_read;
    if(MF_ULTRALIGHT_IS_NTAG_I2C(instance->data->type)) {
        uint8_t tag = 0;
        uint8_t sector = 0;
        uint8_t pages_left = 0;
        if(mf_ultralight_poller_ntag_i2c_addr_lin_to_tag(
               instance, start_page, &sector, &tag, &pages_left)) {
            instance->error =
                mf_ultralight_poller_read_page_from_sector(instance, sector, tag, &data);
        } else {
            FURI_LOG_D(TAG, "Failed to calculate sector and tag from %d page", start_page);
            instance->error = MfUltralightErrorProtocol;
        }
    } else {
        instance->error = mf_ultralight_poller_read_page(instance, start_page, &data);
    }

    if(instance->error == MfUltralightErrorNone) {
        if(start_page < instance->pages_total) {
            FURI_LOG_D(TAG, "Read page %d success", start_page);
            instance->data->page[start_page] = data.page[0];
            instance->pages_read++;
            instance->data->pages_read = instance->pages_read;
        }

        if(instance->pages_read == instance->pages_total) {
            instance->state = MfUltralightPollerStateReadCounters;
        }
    } else {
        if(instance->data->type == MfUltralightTypeMfulC &&
           !mf_ultralight_3des_key_valid(instance->data)) {
            instance->state = MfUltralightPollerStateCheckMfulCAuthStatus;
        } else {
            FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
            if(instance->pages_read) {
                instance->state = MfUltralightPollerStateReadCounters;
            } else {
                instance->state = MfUltralightPollerStateReadFailed;
            }
        }
    }

    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoller* instance) {
    do {
        if(!mf_ultralight_support_feature(
               instance->feature_set, MfUltralightFeatureSupportPasswordAuth))
            break;

        MfUltralightConfigPages* config = NULL;
        mf_ultralight_get_config_page(instance->data, &config);
        if(instance->auth_context.auth_success) {
            config->password = instance->auth_context.password;
            config->pack = instance->auth_context.pack;
        } else if(config->access.authlim == 0) {
            FURI_LOG_D(TAG, "No limits in authentication. Trying default password");
            bit_lib_num_to_bytes_be(
                MF_ULTRALIGHT_DEFAULT_PASSWORD,
                sizeof(MfUltralightAuthPassword),
                instance->auth_context.password.data);
            instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context);
            if(instance->error == MfUltralightErrorNone) {
                FURI_LOG_D(TAG, "Default password detected");
                bit_lib_num_to_bytes_be(
                    MF_ULTRALIGHT_DEFAULT_PASSWORD,
                    sizeof(MfUltralightAuthPassword),
                    config->password.data);
                config->pack = instance->auth_context.pack;
                instance->auth_context.auth_success = true;
            }
        }

        if(instance->pages_read != instance->pages_total) {
            // Probably password protected, fix AUTH0 and PROT so before AUTH0
            // can be written and since AUTH0 won't be readable, like on the
            // original card
            config->auth0 = instance->pages_read;
            config->access.prot = true;
        } else if(!instance->auth_context.auth_success) {
            instance->pages_read -= 2;
            instance->data->pages_read -= 2;
        }
    } while(false);

    instance->state = MfUltralightPollerStateReadSuccess;
    return NfcCommandContinue;
}

static NfcCommand
    mf_ultralight_poller_handler_check_mfuc_auth_status(MfUltralightPoller* instance) {
    instance->state = MfUltralightPollerStateReadSuccess;

    do {
        if(!mf_ultralight_support_feature(
               instance->feature_set, MfUltralightFeatureSupportAuthenticate))
            break;

        if(!instance->auth_context.auth_success) {
            FURI_LOG_D(TAG, "Skip 3des key populating");
            break;
        }

        memcpy(
            &instance->data->page[44],
            instance->auth_context.tdes_key.data,
            MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
        instance->data->pages_read = instance->pages_total;
        instance->pages_read = instance->pages_total;
    } while(false);

    return NfcCommandContinue;
}

static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) {
    FURI_LOG_D(TAG, "Read Failed");
    iso14443_3a_poller_halt(instance->iso14443_3a_poller);
    instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed;
    instance->mfu_event.data->error = instance->error;
    NfcCommand command = instance->callback(instance->general_event, instance->context);
    instance->state = MfUltralightPollerStateIdle;
    return command;
}

static NfcCommand mf_ultralight_poller_handler_read_success(MfUltralightPoller* instance) {
    FURI_LOG_D(TAG, "Read success");
    instance->mfu_event.type = MfUltralightPollerEventTypeReadSuccess;
    NfcCommand command = instance->callback(instance->general_event, instance->context);

    if(instance->mode == MfUltralightPollerModeRead) {
        iso14443_3a_poller_halt(instance->iso14443_3a_poller);
        instance->state = MfUltralightPollerStateIdle;
    } else {
        instance->state = MfUltralightPollerStateRequestWriteData;
    }

    return command;
}

static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPoller* instance) {
    FURI_LOG_D(TAG, "Check writing capability");
    NfcCommand command = NfcCommandContinue;
    MfUltralightPollerState next_state = MfUltralightPollerStateWritePages;
    instance->current_page = 4;

    instance->mfu_event.type = MfUltralightPollerEventTypeRequestWriteData;
    instance->callback(instance->general_event, instance->context);

    // Save write_data to instance field before any further events clobber the union
    instance->write_data = instance->mfu_event.data->write_data;
    const MfUltralightData* write_data = instance->write_data;
    const MfUltralightData* tag_data = instance->data;
    uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type);
    instance->write_skip_key = false;

    bool check_passed = false;
    do {
        if(write_data->type != tag_data->type) {
            FURI_LOG_D(TAG, "Incorrect tag type");
            instance->mfu_event.type = MfUltralightPollerEventTypeCardMismatch;
            break;
        }

        if(mf_ultralight_support_feature(features, MfUltralightFeatureSupportPasswordAuth)) {
            if(!instance->auth_context.auth_success) {
                FURI_LOG_D(TAG, "Unknown password");
                instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked;
                break;
            }
        }

        const MfUltralightPage staticlock_page = tag_data->page[2];
        if(staticlock_page.data[2] != 0 || staticlock_page.data[3] != 0) {
            FURI_LOG_D(TAG, "Static lock bits are set");
            instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked;
            break;
        }

        if(mf_ultralight_support_feature(features, MfUltralightFeatureSupportDynamicLock)) {
            uint8_t dynlock_num = mf_ultralight_get_config_page_num(tag_data->type) - 1;
            const MfUltralightPage dynlock_page = tag_data->page[dynlock_num];
            if(dynlock_page.data[0] != 0 || dynlock_page.data[1] != 0) {
                FURI_LOG_D(TAG, "Dynamic lock bits are set");
                instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked;
                break;
            }
        }

        check_passed = true;
    } while(false);

    // ULC: authenticate the target card before writing.
    // The read phase left the card unauthenticated (write-mode callback skips read-phase auth).
    // Ask callback for keys (cache first, then dict) until one authenticates, then fire
    // WriteKeyRequest so the callback can cache the found key and return the skip decision.
    if(check_passed &&
       mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) {
        bool auth_ok = false;

        while(!auth_ok) {
            // Request next key from callback (tries dict entries)
            memset(instance->mfu_event.data, 0, sizeof(MfUltralightPollerEventData));
            instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
            instance->callback(instance->general_event, instance->context);

            if(!instance->mfu_event.data->key_request_data.key_provided) {
                FURI_LOG_D(TAG, "ULC write: all keys exhausted");
                break;
            }
            instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;

            // Halt+activate so the card is in a clean state for auth
            iso14443_3a_poller_halt(instance->iso14443_3a_poller);
            if(iso14443_3a_poller_activate(instance->iso14443_3a_poller, NULL) !=
               Iso14443_3aErrorNone) {
                FURI_LOG_E(TAG, "ULC write: card not responding (locked out?)");
                break;
            }

            uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
            uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
            furi_hal_random_fill_buf(RndA, sizeof(RndA));
            if(mf_ultralight_poller_authenticate_start(instance, RndA, output) !=
               MfUltralightErrorNone) {
                break;
            }
            uint8_t decoded_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
            const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
            if(mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_RndA) !=
               MfUltralightErrorNone)
                continue;
            mf_ultralight_3des_shift_data(RndA);
            auth_ok = (memcmp(RndA, decoded_RndA, sizeof(decoded_RndA)) == 0);
            FURI_LOG_D(TAG, "ULC write auth attempt: %s", auth_ok ? "success" : "fail");
        }

        if(auth_ok) {
            // Notify callback with the found key: it caches it and returns the skip decision
            MfUltralightC3DesAuthKey found_key = instance->auth_context.tdes_key;
            memset(instance->mfu_event.data, 0, sizeof(MfUltralightPollerEventData));
            instance->mfu_event.data->key_request_data.key = found_key;
            instance->mfu_event.data->key_request_data.key_provided = true;
            instance->mfu_event.type = MfUltralightPollerEventTypeWriteKeyRequest;
            instance->callback(instance->general_event, instance->context);
            instance->write_skip_key = instance->mfu_event.data->write_key_skip;
            FURI_LOG_D(
                TAG,
                "ULC write: key %s",
                instance->write_skip_key ? "kept (target unchanged)" : "overwrite with source");
        } else {
            FURI_LOG_E(TAG, "ULC write auth failed - card locked");
            check_passed = false;
            instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked;
        }
    }

    if(!check_passed) {
        iso14443_3a_poller_halt(instance->iso14443_3a_poller);
        command = instance->callback(instance->general_event, instance->context);
        next_state = MfUltralightPollerStateWriteFail;
    }

    instance->state = next_state;
    return command;
}

static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* instance) {
    NfcCommand command = NfcCommandContinue;

    do {
        // Use the saved write_data pointer - the union was overwritten by WriteKeyRequest
        const MfUltralightData* write_data = instance->write_data;
        uint8_t end_page = mf_ultralight_get_write_end_page(write_data->type);

        // If user chose to keep target's key, stop before the ULC key pages (44-47)
        if(instance->write_skip_key && write_data->type == MfUltralightTypeMfulC &&
           end_page > 44) {
            end_page = 44;
        }

        if(instance->current_page == end_page) {
            instance->state = MfUltralightPollerStateWriteSuccess;
            break;
        }

        // For ULC key pages (44-47): byte-order correction required.
        // Flipper stores each 8-byte DES sub-key MSB-first; the card expects each half reversed.
        // Transform: card_bytes = reverse(flipper[0..7]) || reverse(flipper[8..15])
        MfUltralightPage page_to_write;
        if(instance->current_page >= 44 && instance->current_page <= 47 &&
           write_data->type == MfUltralightTypeMfulC) {
            const uint8_t* raw = (const uint8_t*)&write_data->page[44];
            uint8_t xformed[16];
            for(int i = 0; i < 8; i++) {
                xformed[i] = raw[7 - i];
            }
            for(int i = 0; i < 8; i++) {
                xformed[8 + i] = raw[15 - i];
            }
            uint8_t page_offset = (instance->current_page - 44) * 4;
            memcpy(page_to_write.data, xformed + page_offset, 4);
            FURI_LOG_D(
                TAG,
                "Writing KEY page %d (byte-order corrected): %02X %02X %02X %02X",
                instance->current_page,
                page_to_write.data[0],
                page_to_write.data[1],
                page_to_write.data[2],
                page_to_write.data[3]);
        } else {
            page_to_write = write_data->page[instance->current_page];
            FURI_LOG_D(TAG, "Writing page %d", instance->current_page);
        }

        MfUltralightError error =
            mf_ultralight_poller_write_page(instance, instance->current_page, &page_to_write);
        if(error != MfUltralightErrorNone) {
            instance->state = MfUltralightPollerStateWriteFail;
            instance->error = error;
            break;
        }
        instance->current_page++;
    } while(false);

    return command;
}

static NfcCommand mf_ultralight_poller_handler_write_fail(MfUltralightPoller* instance) {
    FURI_LOG_D(TAG, "Write failed");
    iso14443_3a_poller_halt(instance->iso14443_3a_poller);
    instance->mfu_event.data->error = instance->error;
    instance->mfu_event.type = MfUltralightPollerEventTypeWriteFail;
    NfcCommand command = instance->callback(instance->general_event, instance->context);
    return command;
}

static NfcCommand mf_ultralight_poller_handler_write_success(MfUltralightPoller* instance) {
    FURI_LOG_D(TAG, "Write success");
    iso14443_3a_poller_halt(instance->iso14443_3a_poller);
    instance->mfu_event.type = MfUltralightPollerEventTypeWriteSuccess;
    NfcCommand command = instance->callback(instance->general_event, instance->context);
    return command;
}

static const MfUltralightPollerReadHandler
    mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = {
        [MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle,
        [MfUltralightPollerStateRequestMode] = mf_ultralight_poller_handler_request_mode,
        [MfUltralightPollerStateReadVersion] = mf_ultralight_poller_handler_read_version,
        [MfUltralightPollerStateDetectMfulC] = mf_ultralight_poller_handler_check_ultralight_c,
        [MfUltralightPollerStateDetectNtag203] = mf_ultralight_poller_handler_check_ntag_203,
        [MfUltralightPollerStateGetFeatureSet] = mf_ultralight_poller_handler_get_feature_set,
        [MfUltralightPollerStateReadSignature] = mf_ultralight_poller_handler_read_signature,
        [MfUltralightPollerStateReadCounters] = mf_ultralight_poller_handler_read_counters,
        [MfUltralightPollerStateReadTearingFlags] =
            mf_ultralight_poller_handler_read_tearing_flags,
        [MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth,
        [MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass,
        [MfUltralightPollerStateCheckMfulCAuthStatus] =
            mf_ultralight_poller_handler_check_mfuc_auth_status,
        [MfUltralightPollerStateAuthMfulC] = mf_ultralight_poller_handler_auth_ultralight_c,
        [MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages,
        [MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail,
        [MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success,
        [MfUltralightPollerStateRequestWriteData] =
            mf_ultralight_poller_handler_request_write_data,
        [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages,
        [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail,
        [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success,
};

static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) {
    furi_assert(context);
    furi_assert(event.event_data);
    furi_assert(event.protocol == NfcProtocolIso14443_3a);

    MfUltralightPoller* instance = context;
    furi_assert(instance->callback);

    const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;

    NfcCommand command = NfcCommandContinue;

    if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
        command = mf_ultralight_poller_read_handler[instance->state](instance);
    } else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
        instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed;
        command = instance->callback(instance->general_event, instance->context);
    }

    return command;
}

static bool mf_ultralight_poller_detect(NfcGenericEvent event, void* context) {
    furi_assert(context);
    furi_assert(event.event_data);
    furi_assert(event.protocol == NfcProtocolIso14443_3a);

    bool protocol_detected = false;
    MfUltralightPoller* instance = context;
    const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;

    if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
        MfUltralightPageReadCommandData read_page_cmd_data = {};
        MfUltralightError error = mf_ultralight_poller_read_page(instance, 0, &read_page_cmd_data);
        protocol_detected = (error == MfUltralightErrorNone);
        iso14443_3a_poller_halt(instance->iso14443_3a_poller);
    }

    return protocol_detected;
}

const NfcPollerBase mf_ultralight_poller = {
    .alloc = (NfcPollerAlloc)mf_ultralight_poller_alloc,
    .free = (NfcPollerFree)mf_ultralight_poller_free,
    .set_callback = (NfcPollerSetCallback)mf_ultralight_poller_set_callback,
    .run = (NfcPollerRun)mf_ultralight_poller_run,
    .detect = (NfcPollerDetect)mf_ultralight_poller_detect,
    .get_data = (NfcPollerGetData)mf_ultralight_poller_get_data,
};
