#include "app_core/document_export.h"
#include "test_harness.h"

#include <string>
#include <string_view>

namespace {

class FakeDocumentExportMenuServices final : public pp::app::DocumentExportMenuServices {
public:
    void show_jpeg_dialog() override { jpeg_dialogs += 1; }
    void show_png_dialog() override { png_dialogs += 1; }
    void show_layers_dialog() override { layer_dialogs += 1; }
    void show_cube_faces_dialog() override { cube_dialogs += 1; }
    void show_depth_dialog() override { depth_dialogs += 1; }
    void show_animation_frames_dialog() override { animation_frame_dialogs += 1; }
    void show_animation_mp4_dialog() override { animation_mp4_dialogs += 1; }
    void show_timelapse_dialog() override { timelapse_dialogs += 1; }
    void show_license_disabled() override { license_messages += 1; }

    [[nodiscard]] int total_calls() const noexcept
    {
        return jpeg_dialogs + png_dialogs + layer_dialogs + cube_dialogs + depth_dialogs
            + animation_frame_dialogs + animation_mp4_dialogs + timelapse_dialogs
            + license_messages;
    }

    int jpeg_dialogs = 0;
    int png_dialogs = 0;
    int layer_dialogs = 0;
    int cube_dialogs = 0;
    int depth_dialogs = 0;
    int animation_frame_dialogs = 0;
    int animation_mp4_dialogs = 0;
    int timelapse_dialogs = 0;
    int license_messages = 0;
};

class FakeDocumentExportServices final : public pp::app::DocumentExportServices {
public:
    bool create_directory(std::string_view directory) override
    {
        create_dir_calls += 1;
        last_directory = std::string(directory);
        return create_directory_result;
    }

    void export_equirectangular(const pp::app::DocumentExportFileTarget& target) override
    {
        equirectangular_exports += 1;
        last_path = target.path;
        call_order += "equirect;";
    }

    void export_layers_to_stem(const pp::app::DocumentExportStemTarget& target) override
    {
        layer_stem_exports += 1;
        last_stem = target.stem_path;
        call_order += "layers-stem;";
    }

    void export_layers_to_collection(const pp::app::DocumentExportCollectionTarget& target) override
    {
        layer_collection_exports += 1;
        last_stem = target.stem_path;
        call_order += "layers-collection;";
    }

    void export_animation_frames_to_stem(const pp::app::DocumentExportStemTarget& target) override
    {
        frame_stem_exports += 1;
        last_stem = target.stem_path;
        call_order += "frames-stem;";
    }

    void export_animation_frames_to_collection(const pp::app::DocumentExportCollectionTarget& target) override
    {
        frame_collection_exports += 1;
        last_stem = target.stem_path;
        call_order += "frames-collection;";
    }

    void export_depth(std::string_view document_name) override
    {
        depth_exports += 1;
        last_name = std::string(document_name);
        call_order += "depth;";
    }

    void export_cube_faces(std::string_view document_name) override
    {
        cube_exports += 1;
        last_name = std::string(document_name);
        call_order += "cube;";
    }

    bool create_directory_result = true;
    int create_dir_calls = 0;
    int equirectangular_exports = 0;
    int layer_stem_exports = 0;
    int layer_collection_exports = 0;
    int frame_stem_exports = 0;
    int frame_collection_exports = 0;
    int depth_exports = 0;
    int cube_exports = 0;
    std::string last_directory;
    std::string last_path;
    std::string last_stem;
    std::string last_name;
    std::string call_order;
};

class FakeDocumentVideoExportServices final : public pp::app::DocumentVideoExportServices {
public:
    void export_animation_mp4(std::string_view path) override
    {
        animation_exports += 1;
        last_path = std::string(path);
        call_order += "animation-export;";
    }

    void export_timelapse_mp4(std::string_view path) override
    {
        timelapse_exports += 1;
        last_path = std::string(path);
        call_order += "timelapse-export;";
    }

    void show_animation_export_success(std::string_view path) override
    {
        animation_messages += 1;
        last_message_path = std::string(path);
        call_order += "animation-message;";
    }

    void show_timelapse_export_success(std::string_view path) override
    {
        timelapse_messages += 1;
        last_message_path = std::string(path);
        call_order += "timelapse-message;";
    }

    int animation_exports = 0;
    int timelapse_exports = 0;
    int animation_messages = 0;
    int timelapse_messages = 0;
    std::string last_path;
    std::string last_message_path;
    std::string call_order;
};

void equirectangular_export_builds_file_target(pp::tests::Harness& harness)
{
    const auto target = pp::app::make_document_export_file_target("D:/Paint", "demo", ".png");
    PP_EXPECT(harness, target);
    PP_EXPECT(harness, target.value().path == "D:/Paint/demo.png");
    PP_EXPECT(harness, target.value().suggested_name == "demo.png");
}

void export_file_target_rejects_invalid_input(pp::tests::Harness& harness)
{
    const auto missing_name = pp::app::make_document_export_file_target("D:/Paint", "", ".jpg");
    const auto missing_extension = pp::app::make_document_export_file_target("D:/Paint", "demo", "");
    PP_EXPECT(harness, !missing_name);
    PP_EXPECT(harness, missing_name.status().code == pp::foundation::StatusCode::invalid_argument);
    PP_EXPECT(harness, !missing_extension);
    PP_EXPECT(harness, missing_extension.status().code == pp::foundation::StatusCode::invalid_argument);
}

void collection_export_builds_directory_and_stem(pp::tests::Harness& harness)
{
    const auto target = pp::app::make_document_export_collection_target("D:/Paint", "demo", "_layers");
    PP_EXPECT(harness, target);
    PP_EXPECT(harness, target.value().directory == "D:/Paint/demo_layers");
    PP_EXPECT(harness, target.value().stem_path == "D:/Paint/demo_layers/demo");
}

void picked_directory_export_builds_stem(pp::tests::Harness& harness)
{
    const auto target = pp::app::make_document_export_stem_target("D:/Exports", "demo");
    PP_EXPECT(harness, target);
    PP_EXPECT(harness, target.value().stem_path == "D:/Exports/demo");
}

void video_export_builds_suggested_name(pp::tests::Harness& harness)
{
    const auto timelapse = pp::app::make_document_export_suggested_name("demo", "-timelapse");
    const auto animation = pp::app::make_document_export_suggested_name("demo", "-animation");
    PP_EXPECT(harness, timelapse);
    PP_EXPECT(harness, animation);
    PP_EXPECT(harness, timelapse.value().name == "demo-timelapse");
    PP_EXPECT(harness, animation.value().name == "demo-animation");
}

void collection_export_target_plan_selects_platform_destination(pp::tests::Harness& harness)
{
    const auto layer_collection = pp::app::plan_document_export_collection_target(
        pp::app::DocumentExportCollectionKind::layers,
        true);
    const auto frame_collection = pp::app::plan_document_export_collection_target(
        pp::app::DocumentExportCollectionKind::animation_frames,
        true);
    const auto picked_layers = pp::app::plan_document_export_collection_target(
        pp::app::DocumentExportCollectionKind::layers,
        false);

    PP_EXPECT(
        harness,
        layer_collection.destination == pp::app::DocumentExportCollectionDestination::work_directory_collection);
    PP_EXPECT(
        harness,
        frame_collection.destination == pp::app::DocumentExportCollectionDestination::work_directory_collection);
    PP_EXPECT(
        harness,
        picked_layers.destination == pp::app::DocumentExportCollectionDestination::picked_directory_stem);
    PP_EXPECT(harness, layer_collection.suffix == "_layers");
    PP_EXPECT(harness, frame_collection.suffix == "_frames");
    PP_EXPECT(harness, picked_layers.suffix == "_layers");
}

void export_start_allows_valid_canvas_state(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        pp::app::plan_document_export_start(true, true, true)
            == pp::app::DocumentExportStartDecision::start_now);
    PP_EXPECT(
        harness,
        pp::app::plan_document_export_start(false, false, true)
            == pp::app::DocumentExportStartDecision::start_now);
}

void export_start_blocks_demo_only_when_license_required(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        pp::app::plan_document_export_start(true, false, true)
            == pp::app::DocumentExportStartDecision::show_license_disabled);
}

void export_start_reports_missing_canvas_after_license_gate(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        pp::app::plan_document_export_start(false, true, false)
            == pp::app::DocumentExportStartDecision::unavailable_no_canvas);
    PP_EXPECT(
        harness,
        pp::app::plan_document_export_start(true, false, false)
            == pp::app::DocumentExportStartDecision::show_license_disabled);
}

void export_menu_routes_image_dialogs_without_license(pp::tests::Harness& harness)
{
    const auto jpeg = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::jpeg,
        true,
        false);
    const auto png = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::png,
        true,
        false);

    PP_EXPECT(harness, jpeg.action == pp::app::DocumentExportMenuAction::show_jpeg_dialog);
    PP_EXPECT(harness, jpeg.opens_dialog);
    PP_EXPECT(harness, png.action == pp::app::DocumentExportMenuAction::show_png_dialog);
    PP_EXPECT(harness, png.opens_dialog);
}

void export_menu_blocks_video_when_license_is_missing(pp::tests::Harness& harness)
{
    const auto mp4 = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::animation_mp4,
        true,
        false);
    const auto timelapse = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::timelapse,
        true,
        false);

    PP_EXPECT(harness, mp4.action == pp::app::DocumentExportMenuAction::show_license_disabled);
    PP_EXPECT(harness, !mp4.opens_dialog);
    PP_EXPECT(harness, timelapse.action == pp::app::DocumentExportMenuAction::show_license_disabled);
    PP_EXPECT(harness, !timelapse.opens_dialog);
}

void export_menu_reports_missing_canvas_for_unlicensed_image_exports(pp::tests::Harness& harness)
{
    const auto plan = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::layers,
        false,
        true);

    PP_EXPECT(harness, plan.action == pp::app::DocumentExportMenuAction::unavailable_no_canvas);
    PP_EXPECT(harness, !plan.opens_dialog);
}

void export_menu_executor_dispatches_all_dialog_actions(pp::tests::Harness& harness)
{
    FakeDocumentExportMenuServices services;

    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::jpeg,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::png,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::layers,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::cube_faces,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::depth,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::animation_frames,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::animation_mp4,
                true,
                true),
            services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_menu_plan(
            pp::app::plan_document_export_menu_action(
                pp::app::DocumentExportMenuKind::timelapse,
                true,
                true),
            services).ok());

    PP_EXPECT(harness, services.jpeg_dialogs == 1);
    PP_EXPECT(harness, services.png_dialogs == 1);
    PP_EXPECT(harness, services.layer_dialogs == 1);
    PP_EXPECT(harness, services.cube_dialogs == 1);
    PP_EXPECT(harness, services.depth_dialogs == 1);
    PP_EXPECT(harness, services.animation_frame_dialogs == 1);
    PP_EXPECT(harness, services.animation_mp4_dialogs == 1);
    PP_EXPECT(harness, services.timelapse_dialogs == 1);
}

void export_menu_executor_preserves_blocked_and_unavailable_actions(pp::tests::Harness& harness)
{
    FakeDocumentExportMenuServices services;

    const auto blocked = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::animation_mp4,
        true,
        false);
    PP_EXPECT(harness, blocked.action == pp::app::DocumentExportMenuAction::show_license_disabled);
    PP_EXPECT(harness, pp::app::execute_document_export_menu_plan(blocked, services).ok());
    PP_EXPECT(harness, services.license_messages == 1);

    const auto unavailable = pp::app::plan_document_export_menu_action(
        pp::app::DocumentExportMenuKind::png,
        false,
        true);
    PP_EXPECT(harness, unavailable.action == pp::app::DocumentExportMenuAction::unavailable_no_canvas);
    PP_EXPECT(harness, pp::app::execute_document_export_menu_plan(unavailable, services).ok());
    PP_EXPECT(harness, services.total_calls() == 1);
}

void export_executor_dispatches_file_stem_collection_and_named_exports(pp::tests::Harness& harness)
{
    FakeDocumentExportServices services;
    const auto file = pp::app::make_document_export_file_target("D:/Paint", "demo", ".png");
    const auto collection = pp::app::make_document_export_collection_target("D:/Paint", "demo", "_layers");
    const auto stem = pp::app::make_document_export_stem_target("D:/Exports", "demo");

    PP_EXPECT(harness, file);
    PP_EXPECT(harness, collection);
    PP_EXPECT(harness, stem);
    PP_EXPECT(harness, pp::app::execute_document_export_file(file.value(), services).ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_stem(
            pp::app::DocumentExportCollectionKind::layers,
            stem.value(),
            services)
            .ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_stem(
            pp::app::DocumentExportCollectionKind::animation_frames,
            stem.value(),
            services)
            .ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_collection(
            pp::app::DocumentExportCollectionKind::layers,
            collection.value(),
            services)
            .ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_collection(
            pp::app::DocumentExportCollectionKind::animation_frames,
            collection.value(),
            services)
            .ok());
    PP_EXPECT(harness, pp::app::execute_document_export_depth("demo", services).ok());
    PP_EXPECT(harness, pp::app::execute_document_export_cube_faces("demo", services).ok());

    PP_EXPECT(harness, services.create_dir_calls == 2);
    PP_EXPECT(harness, services.equirectangular_exports == 1);
    PP_EXPECT(harness, services.layer_stem_exports == 1);
    PP_EXPECT(harness, services.layer_collection_exports == 1);
    PP_EXPECT(harness, services.frame_stem_exports == 1);
    PP_EXPECT(harness, services.frame_collection_exports == 1);
    PP_EXPECT(harness, services.depth_exports == 1);
    PP_EXPECT(harness, services.cube_exports == 1);
    PP_EXPECT(
        harness,
        services.call_order
            == "equirect;layers-stem;frames-stem;layers-collection;frames-collection;depth;cube;");
}

void export_executor_preserves_failed_directory_creation(pp::tests::Harness& harness)
{
    FakeDocumentExportServices services;
    services.create_directory_result = false;
    const auto collection = pp::app::make_document_export_collection_target("D:/Paint", "demo", "_layers");

    PP_EXPECT(harness, collection);
    PP_EXPECT(
        harness,
        pp::app::execute_document_export_collection(
            pp::app::DocumentExportCollectionKind::layers,
            collection.value(),
            services)
            .ok());
    PP_EXPECT(harness, services.create_dir_calls == 1);
    PP_EXPECT(harness, services.layer_collection_exports == 0);
    PP_EXPECT(harness, services.call_order.empty());
}

void export_executor_rejects_malformed_targets(pp::tests::Harness& harness)
{
    FakeDocumentExportServices services;

    PP_EXPECT(
        harness,
        !pp::app::execute_document_export_file(pp::app::DocumentExportFileTarget {}, services).ok());
    PP_EXPECT(
        harness,
        !pp::app::execute_document_export_stem(
            pp::app::DocumentExportCollectionKind::layers,
            pp::app::DocumentExportStemTarget {},
            services)
             .ok());
    PP_EXPECT(
        harness,
        !pp::app::execute_document_export_collection(
            pp::app::DocumentExportCollectionKind::animation_frames,
            pp::app::DocumentExportCollectionTarget {},
            services)
             .ok());
    PP_EXPECT(harness, !pp::app::execute_document_export_depth("", services).ok());
    PP_EXPECT(harness, !pp::app::execute_document_export_cube_faces("", services).ok());
    PP_EXPECT(harness, services.call_order.empty());
}

void video_export_executor_dispatches_animation_and_timelapse(pp::tests::Harness& harness)
{
    FakeDocumentVideoExportServices services;

    PP_EXPECT(
        harness,
        pp::app::execute_document_video_export(
            pp::app::DocumentVideoExportKind::animation_mp4,
            "D:/Paint/animation.mp4",
            services)
            .ok());
    PP_EXPECT(
        harness,
        pp::app::execute_document_video_export(
            pp::app::DocumentVideoExportKind::timelapse,
            "D:/Paint/timelapse.mp4",
            services)
            .ok());

    PP_EXPECT(harness, services.animation_exports == 1);
    PP_EXPECT(harness, services.timelapse_exports == 1);
    PP_EXPECT(harness, services.animation_messages == 1);
    PP_EXPECT(harness, services.timelapse_messages == 1);
    PP_EXPECT(harness, services.last_path == "D:/Paint/timelapse.mp4");
    PP_EXPECT(harness, services.last_message_path == "D:/Paint/timelapse.mp4");
    PP_EXPECT(
        harness,
        services.call_order
            == "animation-export;animation-message;timelapse-export;timelapse-message;");
}

void video_export_executor_rejects_empty_paths(pp::tests::Harness& harness)
{
    FakeDocumentVideoExportServices services;

    PP_EXPECT(
        harness,
        !pp::app::execute_document_video_export(
            pp::app::DocumentVideoExportKind::animation_mp4,
            "",
            services)
             .ok());
    PP_EXPECT(harness, services.call_order.empty());
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("equirectangular export builds file target", equirectangular_export_builds_file_target);
    harness.run("export file target rejects invalid input", export_file_target_rejects_invalid_input);
    harness.run("collection export builds directory and stem", collection_export_builds_directory_and_stem);
    harness.run("picked directory export builds stem", picked_directory_export_builds_stem);
    harness.run("video export builds suggested name", video_export_builds_suggested_name);
    harness.run("collection export target plan selects platform destination", collection_export_target_plan_selects_platform_destination);
    harness.run("export start allows valid canvas state", export_start_allows_valid_canvas_state);
    harness.run("export start blocks demo only when license required", export_start_blocks_demo_only_when_license_required);
    harness.run("export start reports missing canvas after license gate", export_start_reports_missing_canvas_after_license_gate);
    harness.run("export menu routes image dialogs without license", export_menu_routes_image_dialogs_without_license);
    harness.run("export menu blocks video when license is missing", export_menu_blocks_video_when_license_is_missing);
    harness.run("export menu reports missing canvas for unlicensed image exports", export_menu_reports_missing_canvas_for_unlicensed_image_exports);
    harness.run("export menu executor dispatches all dialog actions", export_menu_executor_dispatches_all_dialog_actions);
    harness.run("export menu executor preserves blocked and unavailable actions", export_menu_executor_preserves_blocked_and_unavailable_actions);
    harness.run(
        "export executor dispatches file stem collection and named exports",
        export_executor_dispatches_file_stem_collection_and_named_exports);
    harness.run("export executor preserves failed directory creation", export_executor_preserves_failed_directory_creation);
    harness.run("export executor rejects malformed targets", export_executor_rejects_malformed_targets);
    harness.run("video export executor dispatches animation and timelapse", video_export_executor_dispatches_animation_and_timelapse);
    harness.run("video export executor rejects empty paths", video_export_executor_rejects_empty_paths);
    return harness.finish();
}
