package ru.yandex.autotests.direct.scriptrunner.service.clientdata;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import ru.yandex.autotests.direct.scriptrunner.service.scripts.BulkScriptRunner;
import ru.yandex.autotests.direct.scriptrunner.service.scripts.ScriptDBConfig;
import ru.yandex.autotests.direct.scriptrunner.service.scripts.ScriptRunResult;
import ru.yandex.autotests.direct.scriptrunner.service.scripts.ScriptRunTask;
import ru.yandex.autotests.direct.scriptrunner.service.scripts.ScriptrunnerServerException;
import ru.yandex.autotests.direct.scriptrunner.service.scripts.Scripts;
import ru.yandex.autotests.direct.scriptrunner.service.scripts.TaskVerifier;
import ru.yandex.autotests.direct.scriptrunner.service.utils.DbConfigUtils;

@Service
public class ClientDataService {

    private final Logger log = LoggerFactory.getLogger(ClientDataService.class);
    private static final String PAR_ID = "--par-id";
    private static final int MAX_STD_ID = 10;
    private static final int MAX_FULL_LB_EXPORT_ID = 10;

    private static final List<ParId> DEFAULT_PAR_IDS = new ArrayList<>();
    private static final List<ParId> PAR_IDS = new ArrayList<>();
    static {
        for (int i = 1; i <= MAX_STD_ID; i++) {
            DEFAULT_PAR_IDS.add(new ParId("std:" + i));
        }
        for (int i = 1; i <= MAX_FULL_LB_EXPORT_ID; i++) {
            DEFAULT_PAR_IDS.add(new ParId("full_lb_export:" + i));
        }
        PAR_IDS.addAll(DEFAULT_PAR_IDS);
        PAR_IDS.add(new ParId("stdprice:1"));
        PAR_IDS.add(new ParId("camp:1"));
        PAR_IDS.add(new ParId("heavy:1"));
        PAR_IDS.add(new ParId("fast:1"));
        PAR_IDS.add(new ParId("metrica:1"));
        PAR_IDS.add(new ParId("begun:1"));
        PAR_IDS.add(new ParId("dev1:1"));
        PAR_IDS.add(new ParId("devprice1:1"));
        PAR_IDS.add(new ParId("dev2:1"));
        PAR_IDS.add(new ParId("devprice2:1"));
        PAR_IDS.add(new ParId("buggy:1"));
        PAR_IDS.add(new ParId("camps_only:1"));
        PAR_IDS.add(new ParId("internal_ads:1"));
    }


    private Map<ScriptDBConfig, Map<ParId, BulkScriptRunner>> scriptRunnerMap = new HashMap<>();
    private Lock lock = new ReentrantLock();


    public ScriptRunResult runBsClientData(ScriptRunTask task) {
        try {
            TaskVerifier.checkTask(task);
            setDbHostIfEmpty(task);
            BulkScriptRunner runner = obtainScriptRunner(task);
            return runner.runScript(task.getScriptRunParams());
        } catch (Exception e) {
            log.error("error while running bsClientData.pl. taskParams: " + task.toString() +
                    "\n error: " + e.toString());
            throw  new ScriptrunnerServerException("error while running bsClientData.pl", e);
        }
    }


    private BulkScriptRunner obtainScriptRunner(ScriptRunTask task) {
        lock.lock();
        try {
            Map<ParId, BulkScriptRunner> runners = scriptRunnerMap.get(task.getScriptDBConfig());
            if (runners == null) {
                runners = new HashMap<>();
                scriptRunnerMap.put(task.getScriptDBConfig(), runners);
            }
            ParId parId = extractParId(task);
            if (parId == null) {
                setMinLoadedDefaultParId(task, runners);
            }
            return obtainRunner(parId, runners);
        } finally {
            lock.unlock();
        }
    }

    private void setDbHostIfEmpty(ScriptRunTask task) {
        ScriptDBConfig dbConfig = task.getScriptDBConfig();
        if (dbConfig.getDbHost() == null) {
            dbConfig.setDbHost(DbConfigUtils.getDbHost(task.getScriptRunParams().getHost()));
        }
    }

    private ParId extractParId(ScriptRunTask task) {
        ParId parId = null;
        if(task.getScriptRunParams().getScriptParams().get(PAR_ID) != null){
            String parIdStr = (String) task.getScriptRunParams().getScriptParams().get(PAR_ID);
            parId = new ParId(parIdStr);
            checkParId(parId);
        }
        return parId;
    }

    private void setMinLoadedDefaultParId(ScriptRunTask task, Map<ParId, BulkScriptRunner> runners) {
        ParId parId = obtainMinLoadedDefaultParId(runners);
        task.getScriptRunParams().getScriptParams().put(PAR_ID, parId.toString());
    }

    private void checkParId(ParId parId) {
        if (!PAR_IDS.contains(parId)) {
            throw new IllegalArgumentException("unsupported par-id: " + parId);
        }
    }

    private ParId obtainMinLoadedDefaultParId(Map<ParId, BulkScriptRunner> runners) {
        int min = -1;
        ParId parIdMinLoaded = null;
        for (ParId parId : DEFAULT_PAR_IDS) {
            BulkScriptRunner runner = runners.get(parId);
            if (runner == null) {
                return parId;
            }
            int qs = runner.getQueueSize();
            if (qs == 0) {
                return parId;
            }
            if (min < 0 || qs < min) {
                min = qs;
                parIdMinLoaded = parId;
            }
        }
        return parIdMinLoaded;
    }

    private BulkScriptRunner obtainRunner(ParId parId, Map<ParId, BulkScriptRunner> runners) {
        BulkScriptRunner runner = runners.get(parId);
        if (runner == null) {
            runner = new BulkScriptRunner(Scripts.BS_CLIENT_DATA);
            runners.put(parId, runner);
        }
        return runner;
    }
}
