package be.iminds.ilabt.jfed.ui.javafx.scanner;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Location;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Service;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Testbed;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.Gid;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.ServerType;
import be.iminds.ilabt.jfed.lowlevel.authority.JFedTestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.authority.LegacySfaAuthority;
import be.iminds.ilabt.jfed.lowlevel.authority.LegacyStoredAuthorityList;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.JavaFXLogger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.dropwizard.jackson.Jackson;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextArea;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

/**
 * ScanResultPanelController
 */
public class ScanResultPanelController {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ScanResultPanelController.class);

    @FXML
    private TextArea outputTextArea;
    @FXML
    private Button addButton;

    private final JavaFXLogger logger;
    private final GeniUserProvider geniUserProvider;
    private final JFedPreferences jFedPreferences;

    private Stage stage;
    private Testbed testbed;
    private boolean scanAgain;

    @Inject
    public ScanResultPanelController(JavaFXLogger logger, GeniUserProvider geniUserProvider, JFedPreferences jFedPreferences) {
        this.logger = logger;
        this.geniUserProvider = geniUserProvider;
        this.jFedPreferences = jFedPreferences;
        this.scanAgain = false;
    }

    @FXML
    private void initialize() { }

    public void showJsonSnippet() {
        assert outputTextArea != null;
        if (testbed != null) {
            ObjectMapper MAPPER = Jackson.newObjectMapper();
            String testbedJson = null;
            try {
                testbedJson = MAPPER.writer().with(SerializationFeature.INDENT_OUTPUT).writeValueAsString(testbed);
            } catch (JsonProcessingException e) {
                testbedJson = "Error serializing to JSON:\n"+e.getMessage()+" ("+e.getOriginalMessage()+") @ "+e.getLocation();
            }
            outputTextArea.setText(testbedJson);
        } else
            outputTextArea.setText("No testbed");

        addButton.setVisible(true);
    }
    public void showJsonStandalone() {
        assert outputTextArea != null;
        if (testbed != null) {
            ObjectMapper MAPPER = Jackson.newObjectMapper();
            String testbedsJson = null;
            try {
                testbedsJson = MAPPER.writer().with(SerializationFeature.INDENT_OUTPUT).writeValueAsString(Collections.singletonList(testbed));
            } catch (JsonProcessingException e) {
                testbedsJson = "Error serializing to JSON:\n"+e.getMessage()+" ("+e.getOriginalMessage()+") @ "+e.getLocation();
            }
            outputTextArea.setText(testbedsJson);
        } else
            outputTextArea.setText("No testbed");

        addButton.setVisible(false);
    }


    public static ServerType getServiceServerType(Service service) {
        ServerType.GeniServerRole role = apiToServerRole(service.getApi());
        return new ServerType(role, service.getApiVersion());
    }

    public static ServerType.GeniServerRole apiToServerRole(String api) {
        assert api != null;
        assert !api.isEmpty();
        String normalized = api.toLowerCase().replaceAll("[^A-Za-z0-9]", "");
        assert !normalized.isEmpty();
        if (Objects.equals(normalized, "am")) return ServerType.GeniServerRole.AM;
        if (Objects.equals(normalized, "scs")) return ServerType.GeniServerRole.SCS;
        if (normalized.startsWith("geni")) {
            if (Objects.equals(normalized, "geniam")) return ServerType.GeniServerRole.AM;
            if (Objects.equals(normalized, "genisa")) return ServerType.GeniServerRole.GENI_CH_SA;
            if (Objects.equals(normalized, "genima")) return ServerType.GeniServerRole.GENI_CH_MA;
            if (Objects.equals(normalized, "genich")) return ServerType.GeniServerRole.GENI_CH;
            if (Objects.equals(normalized, "geniscs")) return ServerType.GeniServerRole.SCS;
        }
        if (normalized.startsWith("protogeni")) {
            if (Objects.equals(normalized, "protogenisa")) return ServerType.GeniServerRole.PROTOGENI_SA;
            if (Objects.equals(normalized, "protogenich")) return ServerType.GeniServerRole.PROTOGENI_CH;
        }
        if (normalized.startsWith("fed4fire")) {
            if (Objects.equals(normalized, "fed4firefedmon")) return ServerType.GeniServerRole.Fedmon;
            if (Objects.equals(normalized, "fed4firereputationservice")) return ServerType.GeniServerRole.REPUTATION_SERVICE;
        }
        if (Objects.equals(normalized, "reservationcalendarpage")) return ServerType.GeniServerRole.ReservationCalendarPage;
        if (Objects.equals(normalized, "slacollector")) return ServerType.GeniServerRole.SLA_COLLECTOR;
        if (Objects.equals(normalized, "gts")) return ServerType.GeniServerRole.GTS_REST;
        if (normalized.startsWith("planetlab")) {
            if (Objects.equals(normalized, "planetlabsliceregistry")) return ServerType.GeniServerRole.PlanetLabSliceRegistry;
            if (Objects.equals(normalized, "planetlabsr")) return ServerType.GeniServerRole.PlanetLabSliceRegistry;
        }
        throw new IllegalArgumentException("Invalid server role: \"" + api + "\"  (normalized=" + normalized + ")");
    }

    public static LegacySfaAuthority convertToSfaAuthority(Testbed testbed) {
        String urnString = testbed.getDefaultComponentManagerUrn();
        String hrn = testbed.getLongName();
        Location loc = testbed.getServers().get(0).getLocation();
        LegacySfaAuthority.Location location = loc == null ? null : new LegacySfaAuthority.Location(loc.getCountryCode(), loc.getLatitude(), loc.getLongitude());
        List<LegacySfaAuthority.ProxyInfo> proxies = null;
        Gid gid = null;
        String type = testbed.getServers().get(0).getServerType();
        Map< ServerType, URL > urls = new HashMap<>();
        try {
            for (Service service : testbed.getServers().get(0).getServices()) {
                ServerType serverType = getServiceServerType(service);
                if (serverType == null) {
                    LOG.error("Could not find ServerType matching \""+service.getApi()+"\" for uri "+service.getUri());
                    return null;
                }
                if (service.getUrl() == null) {
                    LOG.error("Service with api \""+service.getApi()+"\" has no url");
                    return null;
                }
                urls.put(serverType, new URL(service.getUrl()));
            }

            LegacySfaAuthority res = new LegacySfaAuthority(urnString, hrn, location, urls, proxies, gid, type);
            return res;
        } catch (JFedException e) {
            LOG.warn("Failed to convert testbed to SfaAuthority", e);
            return null;
        } catch (MalformedURLException e) {
            LOG.warn("Failed to convert testbed to SfaAuthority due to URL problem", e);
            return null;
        }
    }

    public void showXmlSnippet() {
        assert outputTextArea != null;
        if (testbed != null) {
            LegacySfaAuthority authority = convertToSfaAuthority(testbed);
            if (authority == null)
                outputTextArea.setText("Error converting authority");
            else
                outputTextArea.setText(authority.toXmlString(true/*omitDeclaration*/, true/*nonamespace*/));
        } else
            outputTextArea.setText("No authority");

        addButton.setVisible(false);
    }
    public void showXmlStandalone() {
        assert outputTextArea != null;
        if (testbed != null) {
            LegacySfaAuthority authority = convertToSfaAuthority(testbed);
            if (authority == null)
                outputTextArea.setText("Error converting authority");
            else
                outputTextArea.setText(LegacyStoredAuthorityList.toXml(Collections.singletonList(authority), false/*omitDeclaration*/, false/*nonamespace*/));
        }
        else
            outputTextArea.setText("No authority");

        addButton.setVisible(false);
    }

    public void save() {
        FileChooser fc = new FileChooser();
        //set the default open dir
        if (System.getProperty("user.home") != null)
            fc.setInitialDirectory(new File(System.getProperty("user.home")));
        fc.setInitialFileName("new_authority.xml");
        File file = fc.showSaveDialog(null);

        if (file == null)
            return;

        IOUtils.stringToFile(file, outputTextArea.getText());
    }

    private Window getOwner() {
        return addButton.getScene().getWindow();
    }

    public void addToLocalJFed() {
        File dir = JFedTestbedInfoSource.getExtraTestbedsDir();
        Server server = testbed.getServers().get(0);

        String baseName = server.getUrnTld();
        if (baseName == null)
            baseName = server.getDefaultComponentManagerAsGeniUrn() == null ? null : server.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority_withoutSubAuth();
        if (baseName == null)
            try {
                baseName = server.getBaseUrl() == null ? null : new URL(server.getBaseUrl()).getHost();
            } catch (MalformedURLException e) {
                LOG.warn("error is Server baseUrl", e);
            }
        if (server.getServices() != null && !server.getServices().isEmpty()) {
            if (baseName == null)
                try {
                    baseName = server.getServices().get(0).getUrn() == null ? null : new GeniUrn(server.getServices().get(0).getUrn()).getEncodedTopLevelAuthority_withoutSubAuth();
                } catch (GeniUrn.GeniUrnParseException e) {
                    LOG.warn("error is Server service 0 urn", e);
                }
            if (baseName == null)
                try {
                    baseName = server.getServices().get(0).getUrl() == null ? null : new URL(server.getServices().get(0).getUrl()).getHost();
                } catch (MalformedURLException e) {
                    LOG.warn("error is Server service 0 url", e);
                }
        }
        String name = baseName == null ? "newserver" : baseName.replaceAll(Pattern.quote("."), "_");
        File file = new File(dir, name+".json");

        if (file.exists()) {
            Optional<ButtonType> reply = JFDialogs.create()
                    .owner(getOwner())
                    .message("Overwrite existing \"" + file.getAbsolutePath() + "\"?")
                    .masthead("File exists")
                    .showConfirm();

            if (!reply.isPresent() || reply.get() != ButtonType.YES) {
                return;
            }
        }

        try {
            if (!dir.exists())
                dir.mkdir();

            IOUtils.stringToFile(file, outputTextArea.getText());
            System.out.println("Wrote testbed to jFed: " + file.getAbsolutePath());
            LOG.debug("Wrote testbed to jFed: " + file.getAbsolutePath());
            JFDialogs.create()
                    .owner(getOwner())
                    .message("The Authority XML has been successfully stored to the jFed \"extra authorities dir\".\n" +
                            "Filename: "+file.getAbsolutePath())
                    .masthead("Successfully stored info")
                    .showInformation();
        } catch (Exception | AssertionError t) {
            LOG.error("Error writing \""+file.getAbsolutePath()+"\"", t);
            JFDialogs.create()
                    .owner(getOwner())
                    .message("Error writing \"" + file.getAbsolutePath() + "\": "+t.getMessage())
                    .masthead("Error writing file")
                    .showError();
        }
    }

    public void scan() {
        scanAgain = true;
        if (stage != null)
            stage.close();
    }
    public void close() {
        scanAgain = false;
        if (stage != null)
            stage.close();
    }

    public void setTestbed(Testbed testbed) {
        this.testbed = testbed;
    }

    public void setStage(Stage stage) {
        this.stage = stage;
    }

    public boolean isScanAgain() {
        return scanAgain;
    }
}
