package _VisualDVM.ComponentsServer;
import Common.Database.Database;
import Common.Database.Objects.DBObject;
import Common.Database.RepositoryRefuseException;
import Common.Utils.Pair;
import Common.Utils.Utils_;
import _VisualDVM.ComponentsServer.BugReport.BugReport;
import _VisualDVM.ComponentsServer.BugReport.Json.BugReportAdditionJson;
import _VisualDVM.ComponentsServer.BugReportRecipient.BugReportRecipient;
import _VisualDVM.ComponentsServer.BugReportSetting.BugReportSetting;
import _VisualDVM.ComponentsServer.Component.ComponentType;
import _VisualDVM.ComponentsServer.Component.Json.ComponentPublicationInfoJson;
import _VisualDVM.ComponentsServer.Component.Json.ComponentVersionsInfoJson;
import _VisualDVM.ComponentsServer.UserAccount.AccountRole;
import _VisualDVM.ComponentsServer.UserAccount.UserAccount;
import _VisualDVM.Constants;
import _VisualDVM.Global;
import _VisualDVM.GlobalData.RemoteFile.RemoteFile;
import _VisualDVM.Passes.All.ZipFolderPass;
import _VisualDVM.ProjectData.LanguageName;
import _VisualDVM.Repository.EmailMessage;
import _VisualDVM.Repository.Server.RepositoryServer;
import _VisualDVM.Utils;
import org.apache.commons.io.FileUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Date;
import java.util.Vector;
public class ComponentsServer extends RepositoryServer<BugReportsDatabase> {
    public CredentialsDatabase credentials_db = null;
    public ComponentsServer() {
        super(BugReportsDatabase.class);
    }
    @Override
    public String getServerHomeName() {
        return "_sapfor_x64";
    }
    @Override
    public int getPort() {
        return Constants.ComponentsServerPort;
    }
    @Override
    protected void extraBackup(File todayBackUp) {
        zip.Do("BugReports", new File(todayBackUp, "BugReports.zip").getAbsolutePath());
    }
    @Override
    public Database getDb() {
        if (request.arg.equals("credentials"))
            return credentials_db;
        return super.getDb();
    }
    @Override
    protected void afterPublishAction(DBObject object) throws Exception {
        if (object instanceof BugReport) {
            //---
            BugReport bugReport = (BugReport) object;
            if (bugReport.settings != null) {
                for (BugReportSetting setting : bugReport.settings) {
                    setting.bugreport_id = bugReport.id;
                    db.Insert(setting);
                }
            }
            if (bugReport.recipients != null) {
                db.saveBugreportRecipients(bugReport);
            }
            //-->
            Utils_.CheckAndCleanDirectory(bugReport.getHome());
            //-->
            if (bugReport.packed_archive != null) {
                File bugArchive = bugReport.getArchiveFile();
                Utils_.bytesToFile(bugReport.packed_archive, bugArchive);
                bugReport.packed_archive = null;
            }
        }
    }
    @Override
    protected void afterEditAction(DBObject object) throws Exception {
        if (object instanceof BugReport) {
            BugReport bugReport = (BugReport) object;
            if (bugReport.recipients != null) {
                db.bugReportRecipients.ActualizeData(
                        db.getVectorByFK(bugReport, BugReportRecipient.class),
                        bugReport.recipients);
            }
        }
    }
    @Override
    protected void afterDeleteAction(DBObject object) throws Exception {
        if (object instanceof BugReport) {
            BugReport bugReport = (BugReport) object;
            Utils_.forceDeleteWithCheck(bugReport.getHome());
        } else if (object instanceof UserAccount) {
            UserAccount account = (UserAccount) object;
            Utils_.forceDeleteWithCheck(account.getServerKeyFile());
        }
    }
    @Override
    public void ActivateDB() {
        super.ActivateDB();
        try {
            credentials_db = new CredentialsDatabase();
            credentials_db.Activate();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    //-
    void DVMConvertProject() throws Exception {
        String[] args = request.arg.split("\n");
        //-
        String projectName = args[0];
        LanguageName projectLanguage = LanguageName.valueOf(args[1]);
        String options = args[2];
        Vector<String> filesNames = new Vector<>(Arrays.asList(args).subList(3, args.length));
        //-
        File workspace = Utils.getTempFileName("convertation");
        FileUtils.forceMkdir(workspace);
        File archive = new File(workspace, projectName + ".zip");
        request.Unpack(archive);
        File project = new File(workspace, projectName);
        Vector<String> badFiles = new Vector<>();
        if (unzip.Do(archive.getAbsolutePath(), workspace.getAbsolutePath()) && project.exists()) {
            String output = "";
            for (String fileName : filesNames) {
                File program = Paths.get(project.getAbsolutePath(), fileName).toFile();
                //--
                File convertedProgram = Paths.get(program.getParent(), Utils_.getFileNameWithoutExtension(program) + ".DVMH." + (projectLanguage.equals(LanguageName.fortran) ? "f" : "c")).toFile();
                String command = Utils_.DQuotes(Global.componentsServerProperties.default_dvm_drv) + " " + projectLanguage.getDVMCompile() + "dv " + options + " " + Utils_.DQuotes(program.getName());
                //--
                File fileWorkspace = program.getParentFile();
                Process process = Utils.startScript(workspace, fileWorkspace, Utils_.getDateName("convert_script"), command);
                process.waitFor();
                String convertationOut = Utils.readAllOutput(process);
                convertationOut = convertationOut.replace(program.getName(), fileName); //для учета пути.
                if (!convertationOut.isEmpty()) output += convertationOut + "\n";
                try {
                    if (convertedProgram.exists()) {
                        FileUtils.forceDelete(program);
                        convertedProgram.renameTo(program);
                    } else badFiles.add(program.getName());
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                //--
            }
            response.arg = String.join("\n", badFiles) + "|" + output;
            File resultArchive = new File(workspace, projectName + "_result.zip");
            if (ComponentsServer.zip.Do(project.getAbsolutePath(), resultArchive.getAbsolutePath())) {
                response.object = Utils_.fileToBytes(resultArchive);
            } else throw new RepositoryRefuseException("Внутренняя ошибка. Не удалось запаковать версию");
            //--
        } else throw new RepositoryRefuseException("Внутренняя ошибка. Не удалось распаковать проект");
    }
    void AppendBugReportField() throws Exception {
        BugReportAdditionJson transport = (BugReportAdditionJson) request.object;
        checkExistense(transport.id, BugReport.class);
        BugReport actual = db.bugReports.get(transport.id);
        Field field = BugReport.class.getField(transport.fieldName);
        field.set(actual, field.get(actual) + "\n" + transport.textAddition);
        actual.change_date = new Date().getTime();
        db.Update(actual);
        response.object = actual;
    }
    void ReceiveAllArchives() throws Exception {
        ZipFolderPass zip = new ZipFolderPass();
        File archives = new File(Utils_.getDateName("Bugs"));
        if (zip.Do("BugReports", archives.getAbsolutePath())) {
            response.object = Utils_.fileToBytes(archives);
        } else throw new RepositoryRefuseException("Не удалось запаковать архивы");
    }
    void ReceiveBugReportsDatabase() throws Exception {
        response.object = Utils_.fileToBytes(db.getFile());
        response.arg = credentials_db.userAccounts.getPackedActiveRecipients();
    }
    void ReceiveBugReport() throws Exception {
        File bugArchive = Utils_.getFile(Utils_.getHomePath(), "BugReports", request.arg, request.arg + ".zip");
        response.object = Utils_.fileToBytes(bugArchive);
    }
    void UpdateBugReport() throws Exception {
        BugReport oldBugReport = (BugReport) request.object;
        checkExistense(oldBugReport.id, BugReport.class);
        BugReport bugReport = db.bugReports.get(oldBugReport.id);
        bugReport.SynchronizeFields(oldBugReport);
        bugReport.change_date = new Date().getTime();
        db.Update(bugReport);
    }
    //--
    void GetComponentsBackUps() throws Exception {
        File backupsDirectory = Paths.get(Utils_.getHomePath(), "Components", request.arg, "Backups").toFile();
        //--
        if (backupsDirectory.exists()) {
            File[] files = backupsDirectory.listFiles(File::isFile);
            if (files != null) {
                Vector<RemoteFile> res = new Vector<>();
                for (File file : files)
                    res.add(new RemoteFile(file.getAbsolutePath(), false)); //тут всегда линух.
                response.object = res;
            } else throw new RepositoryRefuseException("Не удалось получить список предыдущих версий");
        } else {
            //баги еще не создавались. штатная ситуация.
            response.object = new Vector<>();
        }
    }
    void CheckURLRegistered() throws Exception {
        String[] data = request.arg.split("\n");
        String email = data[0];
        String machineURL = data[1];
        String login = data[2];
        response.object = credentials_db.workspaces.findWorkspace(email, machineURL, login);
    }
    void GetComponentChangesLog() throws Exception {
        response.object = Utils_.fileToBytes(Paths.get(Utils_.getHomePath(), "Components", request.arg, "changes.txt").toFile());
    }
    void PublishComponent() throws Exception {
        ComponentPublicationInfoJson info = (ComponentPublicationInfoJson) request.object;
        File componentHome = Utils_.getFile(Utils_.getHomePath(), "Components", info.componentType.toString());
        File componentFile = new File(componentHome, info.fileName);
        File versionFile = new File(componentHome, "version.txt");
        File backupsFolder = new File(componentHome, "Backups");
        //0 архивация старой версии, если она есть.
        if (componentFile.exists()) {
            String versionText = "";
            if (versionFile.exists()) versionText = Utils.ReadAllText(versionFile);
            //---->>
            Utils_.CheckDirectory(backupsFolder);
            Utils.keepNewFiles(backupsFolder, Global.componentsServerProperties.ComponentsBackUpsCount);
            //-->>
            File backupFile = new File(backupsFolder, info.componentType + "_" + versionText);
            if (backupFile.exists()) Utils_.forceDeleteWithCheck(backupFile);
            FileUtils.moveFile(componentFile, backupFile);
        }
        //1 распаковка компонента
        Utils_.bytesToFile((byte[]) info.packedFile, componentFile);
        //2 запись версии компонента
        FileUtils.writeStringToFile(versionFile, info.versionText);
        //3 запись в журнал компонента
        File changesLog = new File(componentHome, "changes.txt");
        FileWriter writer = new FileWriter(changesLog.getAbsolutePath(), true);
        BufferedWriter bufferWriter = new BufferedWriter(writer);
        bufferWriter.write(info.changeRecord);
        bufferWriter.close();
        //-
        if (info.needsUpdateMinimalVersion) {
            Print("Поднять минимальную версию компонента " + info.componentType);
            File minimal_versionFile = new File(componentHome, "minimal_version.txt");
            FileUtils.writeStringToFile(minimal_versionFile, info.versionText);
            //-
            //3 запись в журнал компонента
            FileWriter writer_ = new FileWriter(changesLog, true);
            BufferedWriter bufferWriter_ = new BufferedWriter(writer_);
            bufferWriter_.write("Минимальная версия поднята до " + info.versionText + "\n");
            bufferWriter_.close();
        }
        //-рассылка об изменениях.
        if (info.needsEmail) {
            String version_mail_header = String.join(" ",
                    "Опубликована версия",
                    Utils_.DQuotes(info.versionNumber),
                    "компонента",
                    Utils_.DQuotes(info.componentType.getDescription()));
            //-
            EmailMessage message =
                    new EmailMessage(version_mail_header,
                            info.changeRecord
                    );
            if (info.needsSendFile)
                message.addAttachement(componentFile);
            //--
            for (String address : credentials_db.userAccounts.getActiveMails()) {
                EmailMessagesQueue.add(new Pair<>(address, message));
            }
        }
    }
    void GetComponentsVersionsInfo() throws Exception {
        Vector<String> types = (Vector<String>) request.object;
        Vector<ComponentVersionsInfoJson> res = new Vector<>();
        for (String sType : types) {
            ComponentType componentType = ComponentType.valueOf(sType);
            File actualVersionFile = Paths.get(Utils_.getHomePath(), "Components", sType, "version.txt").toFile();
            File minimalVersionFile = Paths.get(Utils_.getHomePath(), "Components", sType, "minimal_version.txt").toFile();
            ComponentVersionsInfoJson info = new ComponentVersionsInfoJson(componentType);
            info.actual_version = Utils_.removeCharacters(Utils.ReadAllText(actualVersionFile), "\n", "\r");
            info.minimal_version = Utils_.removeCharacters(Utils.ReadAllText(minimalVersionFile), "\n", "\r");
            res.add(info);
        }
        response.object = res;
    }
    void ReceiveComponent() throws Exception {
        String[] packed1 = request.arg.split("\n");
        //тип/имя файла
        File componentFile1 = Paths.get(Utils_.getHomePath(), "Components", packed1[0], packed1[1]).toFile();
        Print("Получить компонент " + packed1[0]);
        response.object = Utils_.fileToBytes(componentFile1);
    }
    void GetUserAccountByKey() throws Exception {
        UserAccount account = credentials_db.userAccounts.getByKey(request.arg);
        response.object = account == null ? null : account;
    }
    void GetUserAccountByEmail() throws Exception {
        UserAccount account_info = (UserAccount) request.object;
        UserAccount account = credentials_db.userAccounts.getByEmail(account_info.email);
        if (account == null) {
            account = new UserAccount(account_info);
            account.role = Constants.admins_mails.contains(account.email) ? AccountRole.Admin : AccountRole.User;
            credentials_db.Insert(account);
            account.generateKey();
        }
        response.object = account;
        response.arg = FileUtils.readFileToString(account.getServerKeyFile());
    }
    void ReceiveCredentialsDatabase() throws Exception {
        response.object = Utils_.fileToBytes(credentials_db.getFile());
    }
    //--
    @Override
    protected void UnsafeSession() throws Exception {
        switch (code) {
            case GetUserAccountByKey:
                GetUserAccountByKey();
                break;
            case GetUserAccountByEmail:
                GetUserAccountByEmail();
                break;
            case GetComponentsBackups:
                GetComponentsBackUps();
                break;
            case UpdateComponent:
                //zip
                PublishComponent();
                break;
            case ReceiveComponent:
                ReceiveComponent();
                break;
            case GetComponentsVersionsInfo:
                GetComponentsVersionsInfo();
                break;
            case GetComponentChangesLog:
                GetComponentChangesLog();
                break;
            default:
                throw new RepositoryRefuseException("Неподдерживаемый код: " + code);
        }
    }
    @Override
    protected void Session() throws Exception {
        switch (code) {
            case ReceiveCredentialsDatabase:
                ReceiveCredentialsDatabase();
                break;
            case ReceiveAllArchives:
                ReceiveAllArchives();
                break;
            case UpdateBugReport:
                UpdateBugReport();
                break;
            case AppendBugReportTextField:
                AppendBugReportField();
                break;
            case ReceiveBugReportsDatabase:
                ReceiveBugReportsDatabase();
                break;
            case ReceiveBugReport:
                ReceiveBugReport();
                break;
            case DVMConvertProject:
                DVMConvertProject();
                break;
            case CheckURLRegistered:
                CheckURLRegistered();
                break;
            default:
                throw new RepositoryRefuseException("Неподдерживаемый код: " + code);
        }
    }
}
