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

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/QUnit.qm
%requires ../../../../qlib/Mime.qm
%requires ../../../../qlib/MapperUtil.qm
%requires ../../../../qlib/Logger.qm
%requires ../../../../qlib/DataProvider
%requires ../../../../qlib/ConnectionProvider
%requires ../../../../qlib/RestClient.qm
%requires ../../../../qlib/SalesforceRestClient.qm
%requires ../../../../qlib/SalesforceRestDataProvider

%try-module json
%define NoJson
%endtry

%requires qore >= 0.9

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

%exec-class SalesforceTest

class SalesforceTest inherits QUnit::Test {
    public {}

    private {
        SalesforceRestClient rc;

        # salesforce.com connection options
        hash<auto> sfopts;

        Logger logger;

        # command-line options
        const MyOpts = Opts + {
            "client_id":      "i,client_id=s",
            "client_secret":  "s,client_secret=s",
            "user":           "u,username=s",
            "pass":           "p,password=s",
            "timeout":        "t,timeout=i",
        };

        const OptionColumn = 22;

        const AcctNo1 = "DEL_TEST_1";
        const AcctName1 = "My Test Account 1";

        const AcctNo2 = "DEL_TEST_2";
    }

    constructor() : Test("SalesforceTest", "1.0", \ARGV, MyOpts) {
        addTestCase("ping tests", \pingTests());
        addTestCase("Salesforce.com DataProvider test", \salesForceDataProviderTests());
        addTestCase("Salesforce.com REST", \salesForceRestTests());
        addTestCase("connection tests", \connectionTest());

        logger = new Logger("test", (m_options.verbose > 3 ? LoggerLevel::getLevelDebug() : LoggerLevel::getLevelInfo()));
        if (m_options.verbose > 2) {
            logger.addAppender(new StdoutAppender());
        }

        try {
            setupConnection();
        } catch (hash<ExceptionInfo> ex) {
            if (m_options.verbose) {
                printf("%s\n", get_exception_string(ex));
            }
        }

        set_return_value(main());
    }

    private usageIntern() {
        TestReporter::usageIntern(OptionColumn);
        printOption("-i,--client_id=ARG", "set Salesforce.com consumer key", OptionColumn);
        printOption("-p,--pass=ARG", "set Salesforce.com password", OptionColumn);
        printOption("-s,--client_secret=ARG", "set Salesforce.com consumer secret", OptionColumn);
        printOption("-t,--timeout=ARG", "set Salesforce.com timeout in seconds", OptionColumn);
        printOption("-u,--user=ARG", "set Salesforce.com user name", OptionColumn);
    }

    setupConnection() {
        sfopts.client_id = m_options.client_id ?? ENV.SALESFORCE_CONSUMER_KEY;
        if (!sfopts.client_id)
            error("missing --client_id option or SALESFORCE_CONSUMER_KEY environment variable");
        sfopts.client_secret = m_options.client_secret ?? ENV.SALESFORCE_CONSUMER_SECRET;
        if (!sfopts.client_secret)
            error("missing --client_secret option or SALESFORCE_CONSUMER_SECRET environment variable");
        sfopts.username = m_options.user ?? ENV.SALESFORCE_USER;
        if (!sfopts.username)
            error("missing --username option or SALESFORCE_USER environment variable");
        sfopts.password = m_options.pass ?? ENV.SALESFORCE_PASS;
        if (!sfopts.password)
            error("missing --password option or SALESFORCE_PASS environment variable");
        if (sfopts."timeout")
            sfopts.connect_timeout = sfopts.timeout;

        rc = new SalesforceRestClient(sfopts + {"logger": logger});
    }

    private error(string fmt) {
        throw "OPTION-ERROR", sprintf("%s: ERROR: %s\n", get_script_name(), vsprintf(fmt, argv));
    }

    private pingTests() {
        if (!rc) testSkip("no Salesforce.com connection");

        SalesforceRestConnection sfrc("test", "test", rc.getURL(), {}, sfopts);
        assertEq(True, sfrc instanceof SalesforceRestConnection);
        sfrc.setLogger(logger);

        TestPollingConnectionMonitor monitor(logger);
        on_exit delete monitor;

        # first test a standard ping
        hash<PingInfo> ping = sfrc.ping(True);
        assertEq("test", ping.name);
        assertTrue(ping.ok);
        assertEq("OK", ping.info);

        monitor.add(sfrc);

        hash<auto> info = monitor.waitForResult();
        assertEq(info.name, "test");
        on_error if (info.ex) {
            printf("%s\n", get_exception_string(info.ex));
        }
        assertEq("OK", info.result);
    }

    salesForceDataProviderTests() {
        if (!rc) testSkip("no Salesforce.com connection");

        SalesforceRestDataProvider dp(rc);

        assertFalse(dp.requiresTransactionManagement());
        {
            list<string> children = dp.getChildProviderNames();
            assertEq(Type::List, children.type());
            hash<string, bool> child_map = map {$1: True}, children;
            assertTrue(child_map.Account);
        }

        list<hash<auto>> recs;
        {
            AbstractDataProvider account = dp.getChildProvider("Account");

            string id;
            {
                # create new account
                hash<auto> rec += {
                    "Name": "deleteme",
                    "Type": "test-delete",
                };
                if (account.getRecordType().SLAExpirationDate__c) {
                    rec += {
                        "SLAExpirationDate__c": now_us(),
                    };
                }
                id = account.createRecord(rec).id;
            }
            on_exit {
                # delete the record
                assertEq(1, account.deleteRecords({"Name": "deleteme"}));
            }

            # search the record
            recs = map $1, account.searchRecords({"Id": id}, {"limit": 1});
            assertEq(1, recs.size());
            assertEq(id, recs[0].Id);

            recs = map $1, account.searchRecords({"Type": soql_op_in("test-delete", get_random_string())});
            assertEq(1, recs.size());
            assertEq(id, recs[0].Id);

            # update the record
            assertEq(1, account.updateRecords({"Type": "delete"}, {"Id": id}));
        }
    }

    salesForceRestTests() {
        if (!rc) testSkip("no Salesforce.com connection");

        assertEq(Type::String, rc.getApi().type());

        hash h = rc.get("sobjects");
        assertEq(Type::List, h.body.sobjects.type());

        # create a bulk job
        h = rc.bulkJobCreate(BulkJobInsert, "Account", BulkJobCsv).jobInfo;
        assertEq(Type::Hash, h.type());
        assertEq(Type::String, h.id.type());

        # close the bulk job
        hash ch = rc.bulkJobClose(h.id);
        assertEq(h.jobInfo.id, ch.id);

        rc.logout();
        assertEq(NOTHING, rc.getToken());
    }

    connectionTest() {
        string url = sprintf("http://%s:%s@localhost:8080", sfopts.username, sfopts.password);
        remove sfopts{"username", "password"};
        SalesforceRestConnection swsc("test", "test", url, {}, sfopts);
        assertEq(True, swsc instanceof SalesforceRestConnection);

        assertThrows("CONNECTION-ERROR", sub () {
            SalesforceRestConnection swsc1("test", "test", url, {"monitor": True}, {"x": 1});
        });

%ifdef NoJson
        testSkip("no json module present");
%endif

        SalesforceRestClient client = swsc.get(False);
        # issue #3321: check default timeouts
        assertEq(45000, client.getTimeout());
        assertEq(45000, client.getConnectTimeout());

        {
            SalesforceRestConnection rc("test", "test", "http://localhost", {}, {
                "oauth2_grant_type": "authorization_code",
                "client_id": get_random_string(),
                "client_secret": get_random_string(),
                "oauth2_redirect_url": "auto",
            });
            assertEq("json", rc.getRealOptions()."data");
            string uri = rc.getAuthorizationCodeRequest();
            assertTrue(uri.equalPartialPath(SalesforceRestClient::Defaults.oauth_url_auth));
            hash<ConnectionInfo> cinfo = rc.getInfo();
            assertTrue(cinfo.features{AbstractConnection::CF_UPDATE_OPTIONS});
            assertTrue(cinfo.features{RestConnection::RCF_OAUTH2_AUTH_CODE});
        }

        {
            SalesforceRestConnection rc("test", "test", "http://localhost", {}, {
                "oauth2_grant_type": "authorization_code",
                "client_id": get_random_string(),
                "client_secret": get_random_string(),
                "oauth2_redirect_url": "cloud",
            });
            assertEq("json", rc.getRealOptions()."data");
            string uri = rc.getAuthorizationCodeRequest();
            assertTrue(uri.equalPartialPath(SalesforceRestClient::Defaults.oauth_url_auth));
            hash<ConnectionInfo> cinfo = rc.getInfo();
            assertTrue(cinfo.features{AbstractConnection::CF_UPDATE_OPTIONS});
            assertTrue(cinfo.features{RestConnection::RCF_OAUTH2_AUTH_CODE});
        }
    }
}

class TestPollingConnectionMonitor inherits PollingConnectionMonitor {
    private {
        Queue msgq();
    }

    constructor(*Logger logger) : PollingConnectionMonitor(logger) {
        ping_timeout = 5s;
    }

    hash<auto> waitForResult() {
        return msgq.get();
    }

    private handlePingSuccess(string name, date delta, *bool oldok) {
        PollingConnectionMonitor::handlePingSuccess(name, delta, oldok);
        msgq.push({"name": name, "result": "OK"});
    }

    private handlePingFailed(string name, date delta, hash<ExceptionInfo> ex) {
        PollingConnectionMonitor::handlePingFailed(name, delta, ex);
        msgq.push({"name": name, "result": "error", "ex": ex});
    }

    private handlePingTimeout(string name, date delta) {
        PollingConnectionMonitor::handlePingTimeout(name, delta);
        msgq.push({"name": name, "result": "timeout"});
    }
}
