package de.votian.services.service;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import de.votian.services.config.ParameterKeys;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPSClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

@Service
public class LegacyFileTransferService {

    private static final Logger log = LoggerFactory.getLogger(LegacyFileTransferService.class);

    private final ParameterService parameterService;

    public LegacyFileTransferService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    public void upload(String profile, String fileName, byte[] content) {
        TransferProfile transferProfile = loadProfile(profile);
        switch (transferProfile.sslMode()) {
            case "2" -> uploadViaSftp(transferProfile, fileName, content);
            case "1" -> uploadViaFtps(transferProfile, fileName, content);
            default -> uploadViaFtp(transferProfile, fileName, content);
        }
    }

    private TransferProfile loadProfile(String profile) {
        String normalizedProfile = profile != null ? profile.trim().toUpperCase() : "";
        if (normalizedProfile.isBlank()) {
            throw new IllegalArgumentException("FTP-Profil fehlt.");
        }

        String server = loadProfileValue(ParameterKeys.FTP_SERVER_PREFIX, normalizedProfile);
        String user = loadProfileValue(ParameterKeys.FTP_USER_PREFIX, normalizedProfile);
        String password = loadProfileValue(ParameterKeys.FTP_PASSWORD_PREFIX, normalizedProfile);
        String sslMode = blankToDefault(loadProfileValue(ParameterKeys.FTP_SSL, normalizedProfile), "0");
        String remotePath = blankToDefault(loadProfileValue(ParameterKeys.FTP_REMOTEPATH, normalizedProfile), "/");

        if (server.isBlank() || user.isBlank() || password.isBlank()) {
            throw new IllegalStateException("FTP-Profil " + normalizedProfile + " ist unvollstaendig konfiguriert.");
        }

        Endpoint endpoint = parseEndpoint(server, "2".equals(sslMode) ? 22 : 21);
        return new TransferProfile(normalizedProfile, endpoint.host(), endpoint.port(), user, password, sslMode, normalizeDirectory(remotePath));
    }

    private String loadProfileValue(String keyPrefix, String profile) {
        return blankToEmpty(parameterService.getParameterValueWithFallback(keyPrefix + profile, 0L));
    }

    private void uploadViaFtp(TransferProfile profile, String fileName, byte[] content) {
        FTPClient client = new FTPClient();
        try {
            connectAndLogin(client, profile);
            storeFile(client, profile.remotePath(), fileName, content);
        } catch (IOException exception) {
            throw new IllegalStateException("FTP-Upload fuer Profil " + profile.profileName() + " fehlgeschlagen.", exception);
        } finally {
            disconnectQuietly(client);
        }
    }

    private void uploadViaFtps(TransferProfile profile, String fileName, byte[] content) {
        FTPSClient client = new FTPSClient(false);
        try {
            connectAndLogin(client, profile);
            client.execPBSZ(0);
            client.execPROT("P");
            storeFile(client, profile.remotePath(), fileName, content);
        } catch (IOException exception) {
            throw new IllegalStateException("FTPS-Upload fuer Profil " + profile.profileName() + " fehlgeschlagen.", exception);
        } finally {
            disconnectQuietly(client);
        }
    }

    private void connectAndLogin(FTPClient client, TransferProfile profile) throws IOException {
        client.connect(profile.host(), profile.port());
        if (!client.login(profile.user(), profile.password())) {
            throw new IOException("FTP-Login fehlgeschlagen.");
        }
        client.enterLocalPassiveMode();
        client.setFileType(FTP.BINARY_FILE_TYPE);
    }

    private void storeFile(FTPClient client, String remoteDirectory, String fileName, byte[] content) throws IOException {
        ensureFtpDirectory(client, remoteDirectory);
        try (InputStream inputStream = new ByteArrayInputStream(content)) {
            if (!client.storeFile(fileName, inputStream)) {
                throw new IOException("Datei konnte nicht gespeichert werden. Reply-Code: " + client.getReplyCode());
            }
        }
    }

    private void ensureFtpDirectory(FTPClient client, String remoteDirectory) throws IOException {
        String[] segments = remoteDirectory.split("/");
        if (remoteDirectory.startsWith("/")) {
            client.changeWorkingDirectory("/");
        }
        for (String segment : segments) {
            if (segment == null || segment.isBlank()) {
                continue;
            }
            if (!client.changeWorkingDirectory(segment)) {
                if (!client.makeDirectory(segment) || !client.changeWorkingDirectory(segment)) {
                    throw new IOException("FTP-Verzeichnis konnte nicht angelegt werden: " + remoteDirectory);
                }
            }
        }
    }

    private void disconnectQuietly(FTPClient client) {
        if (client == null) {
            return;
        }
        try {
            if (client.isConnected()) {
                client.logout();
                client.disconnect();
            }
        } catch (IOException exception) {
            log.debug("FTP disconnect failed: {}", exception.getMessage());
        }
    }

    private void uploadViaSftp(TransferProfile profile, String fileName, byte[] content) {
        Session session = null;
        ChannelSftp channel = null;
        try {
            JSch jsch = new JSch();
            session = jsch.getSession(profile.user(), profile.host(), profile.port());
            session.setPassword(profile.password());
            Properties properties = new Properties();
            properties.put("StrictHostKeyChecking", "no");
            session.setConfig(properties);
            session.connect();

            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect();
            ensureSftpDirectory(channel, profile.remotePath());
            channel.put(new ByteArrayInputStream(content), fileName);
        } catch (JSchException | SftpException exception) {
            throw new IllegalStateException("SFTP-Upload fuer Profil " + profile.profileName() + " fehlgeschlagen.", exception);
        } finally {
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }

    private void ensureSftpDirectory(ChannelSftp channel, String remoteDirectory) throws SftpException {
        channel.cd("/");
        for (String segment : remoteDirectory.split("/")) {
            if (segment == null || segment.isBlank()) {
                continue;
            }
            try {
                channel.cd(segment);
            } catch (SftpException exception) {
                channel.mkdir(segment);
                channel.cd(segment);
            }
        }
    }

    private String normalizeDirectory(String value) {
        String normalized = value != null ? value.trim() : "";
        if (normalized.isBlank()) {
            return "/";
        }
        normalized = normalized.replace('\\', '/');
        if (!normalized.startsWith("/")) {
            normalized = "/" + normalized;
        }
        while (normalized.endsWith("/") && normalized.length() > 1) {
            normalized = normalized.substring(0, normalized.length() - 1);
        }
        return normalized;
    }

    private Endpoint parseEndpoint(String value, int defaultPort) {
        String normalized = blankToEmpty(value);
        int separator = normalized.lastIndexOf(':');
        if (separator > 0 && separator < normalized.length() - 1 && normalized.indexOf(':') == separator) {
            String host = normalized.substring(0, separator).trim();
            String portValue = normalized.substring(separator + 1).trim();
            if (portValue.matches("\\d+")) {
                return new Endpoint(host, Integer.parseInt(portValue));
            }
        }
        return new Endpoint(normalized, defaultPort);
    }

    private String blankToEmpty(String value) {
        return value != null ? value.trim() : "";
    }

    private String blankToDefault(String value, String fallback) {
        String normalized = blankToEmpty(value);
        return normalized.isBlank() ? fallback : normalized;
    }

    private record TransferProfile(String profileName,
                                   String host,
                                   int port,
                                   String user,
                                   String password,
                                   String sslMode,
                                   String remotePath) {
    }

    private record Endpoint(String host, int port) {
    }
}
