package _VisualDVM.Repository;
import Common.Utils.Utils_;
import _VisualDVM.Constants;
import Common.Database.Objects.DBObject;
import Common.Database.Database;
import _VisualDVM.Global;
import Common.Utils.InterruptThread;
import _VisualDVM.Utils;
import _VisualDVM.Repository.Server.DiagnosticSignalHandler;
import _VisualDVM.Repository.Server.ServerCode;
import _VisualDVM.Repository.Server.ServerExchangeUnit_2021;
import javafx.util.Pair;
import sun.misc.SignalHandler;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Properties;
import java.util.Vector;
public abstract class RepositoryServer<D extends Database> {
    Class<D> d_class;
    protected Socket clientSocket; //сокет для общения
    protected ServerSocket server; // серверсокет
    protected ObjectInputStream in; // поток чтения из сокета
    protected ObjectOutputStream out; // поток записи в сокет
    //-
    public D db;
    protected static FileWriter Log;
    protected ServerExchangeUnit_2021 request;
    protected ServerExchangeUnit_2021 response;
    //-
    protected ServerCode code;
    protected long count = 0; //для отладки.
    protected static boolean printOn = false;
    //-----------
    SignalHandler signalHandler = signal -> {
    };
    //------------
    public abstract int getPort();
    protected abstract void Session() throws Exception;
    protected void startAdditionalThreads() {
    }
    public final static String separator = "----------------------------------";
    public RepositoryServer(Class<D> d_class_in) {
        d_class = d_class_in;
    }
    public void ActivateDB() {
        try {
            db = d_class.newInstance();
            db.Connect();
            db.CreateAllTables();
            db.prepareTablesStatements();
            db.Synchronize();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    protected Thread interruptThread = new InterruptThread(10000,
            () -> {
                System.exit(0);
                return null;
            });
    protected static void Print(String message) {
        if (printOn) {
            try {
                Log = new FileWriter("Log.txt", true);
                String dmessage = Utils_.Brackets("SESSION -> ") + new Date() +
                        " " + message;
                Log.write(dmessage + "\n");
                Log.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    protected void checkTargets(EmailMessage message_in) {
    }
    public void Email(EmailMessage message_in, File... directAttachements) throws Exception {
        checkTargets(message_in);
        Thread thread = new Thread(() -> {
            try {
                Properties props = new Properties();
                props.put("mail.smtp.host", Global.properties.SMTPHost);
                props.put("mail.smtp.auth", "true");
                props.put("mail.smtp.port", String.valueOf(Global.properties.SMTPPort));
                props.put("mail.smtp.socketFactory.port", String.valueOf(Global.properties.MailSocketPort));
                props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
                props.put("mail.smtp.connectiontimeout", String.valueOf(Global.properties.SocketTimeout));
                props.put("mail.smtp.timeout", String.valueOf(Global.properties.SocketTimeout));
                props.put("mail.smtp.writetimeout", String.valueOf(Global.properties.SocketTimeout));
                //------------------------------
                LinkedHashMap<String, File> innerFiles = new LinkedHashMap<>();
                for (String aName : message_in.files.keySet()) {
                    File f = Utils.getTempFileName(aName);
                    Utils_.bytesToFile(message_in.files.get(aName), f);
                    innerFiles.put(aName, f);
                }
                Vector<String> targets_ = new Vector<>(message_in.targets);
                Utils.addDefaultMails(targets_);
                //------------------------------
                Session session = Session.getDefaultInstance(props,
                        new Authenticator() {
                            @Override
                            protected PasswordAuthentication getPasswordAuthentication() {
                                return new PasswordAuthentication(
                                        Constants.MailAddress,
                                        Constants.MailPassword);
                            }
                        });

                for (String target : targets_) {
                    boolean done = false;
                    int attempts = 5;
                    while (!done && (attempts > 0)) {
                        try {
                            MimeMessage message = new MimeMessage(session);
                            message.setFrom(new InternetAddress(Constants.MailAddress));
                            message.setRecipients(Message.RecipientType.CC, InternetAddress.parse(target));
                            message.setSubject(message_in.subject);
                            Multipart multipart = new MimeMultipart();
                            MimeBodyPart textBodyPart = new MimeBodyPart();
                            textBodyPart.setText(message_in.text);
                            multipart.addBodyPart(textBodyPart);
                            for (String aName : innerFiles.keySet()) {
                                MimeBodyPart attachmentBodyPart = new MimeBodyPart();
                                DataSource source = new FileDataSource(innerFiles.get(aName));
                                attachmentBodyPart.setDataHandler(new DataHandler(source));
                                attachmentBodyPart.setFileName(aName);
                                multipart.addBodyPart(attachmentBodyPart);
                            }
                            for (File f : directAttachements) {
                                if (f.exists()) {
                                    MimeBodyPart attachmentBodyPart = new MimeBodyPart();
                                    DataSource source = new FileDataSource(f);
                                    attachmentBodyPart.setDataHandler(new DataHandler(source));
                                    attachmentBodyPart.setFileName(f.getName());
                                    multipart.addBodyPart(attachmentBodyPart);
                                }
                            }
                            message.setContent(multipart);
                            Transport.send(message);
                            done = true;
                        } catch (Exception ex) {
                            System.out.println("Исключение во время отправки сообщения абоненту " + Utils_.Brackets(target));
                            ex.printStackTrace();
                            Utils_.sleep(1000);
                        } finally {
                            attempts--;
                        }
                    }
                }
            } catch (Exception ex) {
                System.out.println("Исключение во время выполнения рассылки.");
                ex.printStackTrace();
            }
        });
        thread.start();
    }
    public boolean canDelete(DBObject object) throws Exception {
        return true;
    }
    public void StartAction() throws Exception {
    }
    public void Start() throws Exception {
        /*
        File started = new File(Constants.STARTED);
        if (started.exists())
            FileUtils.forceDelete(started);
        Utils.createEmptyFile(Constants.STARTED);
         */
        DiagnosticSignalHandler.install("TERM", signalHandler);
        DiagnosticSignalHandler.install("INT", signalHandler);
        DiagnosticSignalHandler.install("ABRT", signalHandler);
        interruptThread.start();
        startAdditionalThreads();
        server = new ServerSocket(getPort());
        Print("Сервер запущен!");
        StartAction();
        while (true) {
            try {
                clientSocket = server.accept();
                Print((count++) + " клиент присоединился, IP=" + clientSocket.getInetAddress());
                code = ServerCode.Undefined;
                out = new ObjectOutputStream(clientSocket.getOutputStream());
                in = new ObjectInputStream(clientSocket.getInputStream());
                //->
                while (true) {
                    DBObject dbObject = null;
                    Pair<Class, Object> p = null;
                    Print("Ожидание команды от клиента...");
                    Object transport = in.readObject();
                    Print("Команда прочитана.");
                    if (transport instanceof ServerExchangeUnit_2021) {
                        request = (ServerExchangeUnit_2021) transport;
                        response = null;
                        Print("клиент_2021: <- " + (request.codeName));
                        try {
                            code = request.getCode();
                            //базовый функционал.
                            switch (code) {
                                //<editor-fold desc="файлы и почта">
                                case ReadFile:
                                    Print("Отправить клиенту текст файла по пути " + Utils_.Brackets(request.arg));
                                    response = new ServerExchangeUnit_2021(ServerCode.OK, "", Utils.ReadAllText(new File(request.arg)));
                                    break;
                                case SendFile:
                                    //нам пришел файл.
                                    Print("Получить от клиента файл, и распаковать его по пути " + Utils_.Brackets(request.arg));
                                    request.Unpack(); //распаковка идет по его аргу-пути назначения
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    break;
                                case ReceiveFile:
                                    Print("Отправить клиенту файл по пути " + Utils_.Brackets(request.arg));
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    File file = new File(request.arg);
                                    response.object = file.exists() ? Utils_.fileToBytes(file) : null;
                                    break;
                                case Email:
                                    Print("Отправка сообщения электронной почты");
                                    Email((EmailMessage) request.object);
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    break;
                                //</editor-fold>
                                //<editor-fold desc="Главная база данных">
                                case CheckObjectExistense:
                                    p = (Pair<Class, Object>) request.object;
                                    Print("Проверить существование объекта класса " + p.getKey().toString() + " с ключом " + p.getValue());
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    response.object = db.checkObjectExistense(p.getKey(), p.getValue());
                                    break;
                                case EditObject:
                                    DBObject new_object = (DBObject) request.object;
                                    Print("Редактировать объект " + new_object.getPK());
                                    db.UpdateWithCheck(new_object);
                                    afterEditAction(new_object);
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    break;
                                case DeleteObject:  //устарело. потом убрать. сейчас на это баг репорты повязаны.
                                    dbObject = (DBObject) request.object;
                                    Print("Удалить объект " + dbObject.getPK());
                                    db.DeleteWithCheck(dbObject);
                                    afterDeleteAction(dbObject);
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    break;
                                case GetObjectCopyByPK:
                                    p = (Pair<Class, Object>) request.object;
                                    Print("Получить копию объекта класса " + p.getKey().toString() + " по ключу " + p.getValue());
                                    dbObject = db.getObjectCopyByPK(p.getKey(), p.getValue());
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    response.object = dbObject;
                                    break;
                                case GetObjectsCopiesByPK:
                                    Print("Получить список копий объектов по ключам");
                                    p = (Pair<Class, Object>) request.object;
                                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                                    response.object = db.getObjectsCopies(p.getKey(), (Vector<Object>) p.getValue());
                                    break;
                                case PublishObject:
                                    PublishObject();
                                    break;
                                case PublishObjects:
                                    PublishObjects();
                                    break;
                                case DeleteObjectByPK:
                                    DeleteObjectByPK();
                                    break;
                                case DeleteObjectsByPK:
                                    DeleteObjectsByPK();
                                    break;
                                //</editor-fold>
                                case EXIT:
                                    Print("ЗАВЕРШИТЬ РАБОТУ СЕРВЕРА");
                                    System.exit(0);
                                    break;
                                case Ping:
                                    Print("Проверка активности сервера");
                                    Ping();
                                    break;
                                default:
                                    Session();
                                    break;
                            }
                        } catch (Exception ex) {
                            response = new ServerExchangeUnit_2021(ServerCode.FAIL, "Исключение сервера", ex);
                        } finally {
                            Print("сервер: -> " + response.codeName);
                            out.writeObject(response);
                            Print("Ответ отправлен.");
                        }
                    }
                }
                //->
            } catch (Exception ex) {
                Print("Соединение с клиентом завершено.");
            } finally {
                //->
                try {
                    if (clientSocket != null)
                        clientSocket.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                // потоки тоже хорошо бы закрыть
                try {
                    if (in != null)
                        in.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                try {
                    if (out != null)
                        out.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                Print("Сервер ждет следующего клиента.");
            }
        }
    }
    //--
    protected Database getDefaultDatabase() {
        return db;
    }
    //--
    private void PublishObject() throws Exception {
        response = new ServerExchangeUnit_2021(ServerCode.OK);
        DBObject dbObject = (DBObject) request.object;
        beforePublishAction(dbObject);
        response.object = (Serializable) getDefaultDatabase().InsertS(dbObject).getPK();
        afterPublishAction(dbObject);
    }
    private void PublishObjects() throws Exception {
        response = new ServerExchangeUnit_2021(ServerCode.OK);
        Vector<DBObject> objects = (Vector<DBObject>) request.object;
        Database database = getDefaultDatabase();
        for (DBObject dbObject : objects) {
            beforePublishAction(dbObject);
            response.object = (Serializable) database.InsertS(dbObject).getPK();
            afterPublishAction(dbObject);
        }
    }
    protected void beforePublishAction(DBObject object) throws Exception {
    }
    protected void afterPublishAction(DBObject object) throws Exception {
    }
    protected void afterEditAction(DBObject object) throws Exception {
    }
    //------------------------------
    private void DeleteObjectByPK() throws Exception {
        Print("Удалить объект по ключу");
        Pair<Class, Object> to_delete = (Pair<Class, Object>) request.object;
        afterDeleteAction(getDefaultDatabase().DeleteByPK(to_delete.getKey(), to_delete.getValue()));
        response = new ServerExchangeUnit_2021(ServerCode.OK);
    }
    private void DeleteObjectsByPK() throws Exception {
        Print("Удалить объекты по ключам");
        Pair<Class, Vector<Object>> to_delete = (Pair<Class, Vector<Object>>) request.object;
        Database database = getDefaultDatabase();
        for (Object object : to_delete.getValue()) {
            afterDeleteAction(database.DeleteByPK(to_delete.getKey(), object));
        }
        response = new ServerExchangeUnit_2021(ServerCode.OK);
    }
    public void afterDeleteAction(DBObject object) throws Exception {
    }
    public void Ping() {
        response = new ServerExchangeUnit_2021(ServerCode.OK);
    }
}
