#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%require-types
%enable-all-warnings
%new-style
%strict-args
%allow-injection

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/QUnit.qm
%requires ../../../../qlib/Logger.qm
%requires ../../../../qlib/DataProvider
%requires ../../../../qlib/RestClient.qm
%requires ../../../../qlib/ElasticSearchDataProvider

%try-module json >= 1.5
%define NoJson
%endtry

%exec-class ElasticSearchDataProviderTest

public class ElasticSearchDataProviderTest inherits QUnit::Test {
    private {
        RestClient rest;

        # command-line options
        const MyOpts = Opts + {
            "url": "u,url=s",
        };

        const OptionColumn = 22;
    }

    constructor() : Test("ElasticSearchDataProvider Test", "1.0") {
        addTestCase("mainTest", \mainTest());

        setupConnection();

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    private mainTest() {
        if (!rest) {
            testSkip("no connection to server");
        }

        string pipeline = get_random_string();
        AbstractDataProvider prov = new ElasticSearchPipelineCreateDataProvider(rest);
        hash<auto> p = {
            "pipeline": pipeline,
            "description": "my pipeline",
            "processors": ({
                "html_strip": {
                    "field": "content",
                }
            },),
        };
        hash<auto> h = prov.doRequest(p);
        assertTrue(h.acknowledged);

        ElasticSearchPipelineDeleteDataProvider delpipe(rest);
        on_exit {
            h = delpipe.doRequest({
                "pipeline": pipeline,
            });
            assertTrue(h.acknowledged);
        }

        prov = new ElasticSearchPipelineReadDataProvider(rest);
        h = prov.doRequest({
            "pipeline": pipeline,
        });
        assertTrue(h.hasKey(pipeline));
        assertEq(p - "pipeline", h{pipeline});

        prov = new ElasticSearchPipelineReadAllDataProvider(rest);
        h = prov.doRequest();
        assertTrue(h.hasKey(pipeline));
        assertEq(p - "pipeline", h{pipeline});

        string index = get_random_string();
        index =~ tr/A-Z/a-z/;
        prov = new ElasticSearchIndexCreateDataProvider(rest);
        h = prov.doRequest({
            "index": index,
            "mappings": {
                "properties": {
                    "content": {
                        "type": "text",
                    },
                },
            },
        });
        assertTrue(h.acknowledged);

        ElasticSearchIndexDeleteDataProvider delindex(rest);
        on_exit {
            h = delindex.doRequest({
                "index": index,
            });
            assertTrue(h.acknowledged);
        }

        prov = new ElasticSearchIndexReadDataProvider(rest);
        h = prov.doRequest({
            "index": index,
        });
        assertTrue(h.hasKey(index));
        hash<auto> index_hash = h{index};

        prov = new ElasticSearchDocumentCreateDataProvider(rest);
        h = prov.doRequest({
            "index": index,
            "pipeline": pipeline,
            "content": "<h2>test</h2>",
            # NOTE: without "refresh=true", the document will not be found in the search below
            "refresh": "true",
        });
        assertEq("created", h.result);
        string docid = h._id;

        ElasticSearchDocumentDeleteDataProvider deldoc(rest);
        on_exit {
            h = deldoc.doRequest({
                "index": index,
                "id": docid,
            });
            assertEq("deleted", h.result);
        }

        prov = new ElasticSearchDocumentReadDataProvider(rest);
        h = prov.doRequest({
            "index": index,
            "id": docid,
        });
        assertEq("\ntest\n", h._source.content);

        prov = new ElasticSearchIndexSearchDataProvider(rest);
        h = prov.doRequest({
            "index": index,
        });
        assertEq(1, h.hits.total.value);

        prov = new ElasticSearchIndexTableDataProvider(rest, index, index_hash);
        auto res = map $1, prov.searchRecords();
        assertEq(Type::List, res.type());
        assertEq(1, res.size());
        assertEq(Type::Hash, res[0].type());
        assertEq(3, res[0].size());
        assertEq(1.0, res[0]._score);
        assertEq("\ntest\n", res[0].content);

        # NOTE: without "refresh=true", the document will not be found in the search below
        assertEq(1, prov.updateRecords({"content": "test2"}, {"_id": docid}, {"refresh": "true"}));
        res = prov.searchSingleRecord({"_id": docid});
        assertEq(Type::Hash, res.type());
        assertEq(3, res.size());
        assertEq(1.0, res._score);
        assertEq("test2", res.content);

        # NOTE: without "refresh=true", the document will not be found in the search below
        string newid = prov.createRecord({"content": "new data"}, {"refresh": "true"})._id;
        res = prov.searchSingleRecord({"_id": newid});
        assertEq(Type::Hash, res.type());
        assertEq(3, res.size());
        assertEq(1.0, res._score);
        assertEq("new data", res.content);

        assertEq(1, prov.deleteRecords({"_id": newid}, {"refresh": "true"}));
        res = prov.searchSingleRecord({"_id": newid});
        assertNothing(res);
    }

    private usageIntern() {
        TestReporter::usageIntern(OptionColumn);
        printOption("-u,--url=ARG", "URL for the ElasticSearch server instance", OptionColumn);
    }

    private setupConnection() {
        *string url = m_options.url ?? ENV.ELASTICSEARCH_URL;
        if (!url) {
            stderr.print("missing --url option or ELASTICSEARCH_URL environment variable\n");
        }

        try {
            rest = new RestClient({"url": url});
        } catch (hash<ExceptionInfo> ex) {
            if (m_options.verbose) {
                stderr.printf("%s: %s\n", ex.err, ex.desc);
            }
        }
    }
}
