package be.iminds.ilabt.jfed.ui.javafx.probe_gui.credential_manipulation;

import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.model.ParameterHistoryModel;
import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.lowlevel.CredentialException;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.SfaCredential;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.JavaFXLogger;
import be.iminds.ilabt.jfed.util.KeyUtil;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.util.Pair;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.File;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;

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

    @FXML
    private BorderPane root;
    @FXML
    private Label spokenForUrnLabel;
    @FXML
    private TextField spokenForUrnText;
    @FXML
    private Label spokenForUrnNoteLabel;

    @FXML
    private Label targetLabel;
    @FXML
    private TextField targetText;
    @FXML
    private RadioButton targetViaUrn;
    @FXML
    private RadioButton targetViaCert;

    @FXML
    private VBox targetUrnBox;
    @FXML
    private VBox targetCertfileBox;

    @FXML
    private TextField targetCertFilename;
    @FXML
    private Button loadCertButton;

    @FXML
    private Button generateButton;
    @FXML
    private Label generateNoteLabel;

    @FXML
    private Label resultLabel;
    @FXML
    private TextArea resultText;
    @FXML
    private Button saveResultButton;

    @FXML
    private Label privNameLabel;
    @FXML
    private TextField privNameText;
    @FXML
    private CheckBox canDelegateCheckBox;
    @FXML
    private Label expireLabel;
    @FXML
    private TextField expireText;

    private final ParameterHistoryModel parameterHistoryModel;

    @Inject
    public SpeaksForSfaCredentialCreatorPanelController(JavaFXLogger logger, AuthorityFinder authorityFinder,
                                                        GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider,
                                                        SfaModel sfaModel, HighLevelTaskFactory hltf,
                                                        TaskThread tt, JFedPreferences jFedPreferences,
                                                        ParameterHistoryModel parameterHistoryModel) {
        super(logger, authorityFinder, geniUserProvider, connectionProvider, sfaModel, hltf, tt, jFedPreferences);
        this.parameterHistoryModel = parameterHistoryModel;
    }


    @FXML
    private void initialize() {
        spokenForUrnLabel.managedProperty().bind(spokenForUrnLabel.visibleProperty());
        spokenForUrnText.managedProperty().bind(spokenForUrnText.visibleProperty());
        spokenForUrnNoteLabel.managedProperty().bind(spokenForUrnNoteLabel.visibleProperty());
        generateButton.managedProperty().bind(generateButton.visibleProperty());
        generateNoteLabel.managedProperty().bind(generateNoteLabel.visibleProperty());
        resultLabel.managedProperty().bind(resultLabel.visibleProperty());
        resultText.managedProperty().bind(resultText.visibleProperty());
        targetUrnBox.managedProperty().bind(targetUrnBox.visibleProperty());
        targetCertfileBox.managedProperty().bind(targetCertfileBox.visibleProperty());

        privNameLabel.managedProperty().bind(privNameLabel.visibleProperty());
        privNameText.managedProperty().bind(privNameText.visibleProperty());
        canDelegateCheckBox.managedProperty().bind(canDelegateCheckBox.visibleProperty());
        expireLabel.managedProperty().bind(expireLabel.visibleProperty());
        expireText.managedProperty().bind(expireText.visibleProperty());

        spokenForUrnLabel.visibleProperty().bind(spokenForUrnText.visibleProperty());
        spokenForUrnNoteLabel.visibleProperty().bind(spokenForUrnText.visibleProperty());
        generateNoteLabel.visibleProperty().bind(generateButton.visibleProperty());
        resultLabel.visibleProperty().bind(resultText.visibleProperty());
        saveResultButton.visibleProperty().bind(resultText.visibleProperty());

        privNameLabel.visibleProperty().bind(privNameText.visibleProperty());
        expireLabel.visibleProperty().bind(expireText.visibleProperty());

        targetUrnBox.visibleProperty().bind(targetViaUrn.selectedProperty());
        targetCertfileBox.visibleProperty().bind(targetViaCert.selectedProperty());


        //TODO: listen for user login/logout sfaModel.getGeniUserProvider().addListener

        updateUser();
    }


    private X509Certificate userCert;

    @FXML
    private void loadCertFile() {
        FileChooser fileChooser = new FileChooser();
        File file = fileChooser.showOpenDialog(null);

        if (file != null) {
            String filename = file.getPath();
            targetCertFilename.setText(filename);
            String fileContent = null;
            try {
                fileContent = IOUtils.fileToString(file);
                userCert = KeyUtil.pemToX509Certificate(fileContent);
            } catch (Exception e) {
                LOG.error("Error reading user certificate file \"" + filename + "\": " + e.getMessage(), e);
                targetCertFilename.setText("ERROR: " + e.getMessage());
                userCert = null;
            }
        }
    }


    private void updateUser() {
        if (geniUserProvider.isUserLoggedIn()) {
            spokenForUrnText.setText(geniUserProvider.getLoggedInGeniUser().getUserUrnString());
            generateButton.setDisable(false);
        } else {
            spokenForUrnText.setText("no user logged in");
            generateButton.setDisable(true);
        }
    }

    protected void setResult(final String text) {
        Platform.runLater(() -> resultText.setText(text));

    }

    Thread generateSpeaksForThread = null;

    public void generate() {
        if (targetViaCert.isSelected() && userCert == null) {
            LOG.info("button clicked: generateDelegate not called since no certificate loaded yet");
            return;
        }

        if (generateSpeaksForThread != null) {
            LOG.debug("button clicked: generateSpeaksFor still running, so not called");
            return;
        } else
            LOG.debug("button clicked: generateSpeaksFor called");

        generateButton.setDisable(true);

        int expireInDaysHelper;
        try {
            expireInDaysHelper = Integer.parseInt(expireText.getText());
        } catch (NumberFormatException e) {
            LOG.debug("Error processing \"expire\": \"" + expireText.getText() + "\" is not a valid integer", e);
            setResult("Error processing \"expire\": \"" + expireText.getText() + "\" is not a valid integer");
            return;
        }
        final int expireInDays = expireInDaysHelper;

        final String privilegeName = privNameText.getText();
        final boolean canDelegate = canDelegateCheckBox.isSelected();

        generateSpeaksForThread = new Thread(() -> {
            String targetUrn;
            if (targetViaUrn.isSelected()) {
                targetUrn = targetText.getText();
                userCert = certificateRetriever(targetUrn);
            } else {
                assert userCert != null;
                targetUrn = findUrnInCertificate(userCert);
                assert targetUrn != null;
            }

            generate_Helper(targetUrn, userCert, expireInDays, privilegeName, canDelegate);
            LOG.trace("generateSpeaksFor_Helper() returned");
            generateSpeaksForThread = null;
            Platform.runLater(() -> generateButton.setDisable(false));
        });
        generateSpeaksForThread.start();
    }

    public void generate_Helper(String speakerUrn, X509Certificate speakerCert, int expireInDays, String privilegeName, boolean canDelegate) {
        assert !Platform.isFxApplicationThread();

        assert geniUserProvider.isUserLoggedIn();

        LOG.trace("generateSpeaksFor_Helper() getting user info");
        Pair<GeniUrn, X509Certificate> userInfo = null;
        try {
            userInfo = getUserInfo(geniUserProvider.getLoggedInGeniUser().getUserUrnString());
        } catch (GeniUrn.GeniUrnParseException e) {
            LOG.error("User urn is invallid (should not occur!)", e);
            setResult("ERROR user urn is invalid: " + geniUserProvider.getLoggedInGeniUser().getUserUrnString());
            return;
        } catch (JFedException e) {
            LOG.error("Error in call for user urn info retrieval (should not occur!)", e);
            setResult("ERROR user urn is invalid: " + geniUserProvider.getLoggedInGeniUser().getUserUrnString());
            return;
        }
//        LOG.trace("generateSpeaksFor_Helper() getting target info");
//        Pair<GeniUrn, X509Certificate> targetInfo = null;
//        try {
//            targetInfo = getUserInfo(speakerUrn);
//            if (targetInfo == null || targetInfo.getValue() == null) {
//                setResult("ERROR getting info for urn: " + speakerUrn);
//                return;
//            }
//        } catch (GeniUrn.GeniUrnParseException e) {
//            LOG.error("ERROR urn is invalid: "+ speakerUrn, e);
//            setResult("ERROR urn is invalid: " + speakerUrn);
//            return;
//        } catch (JFedException e) {
//            LOG.error("Error in call retrieving info for urn: "+ speakerUrn, e);
//            setResult("Error in call retrieving info for urn: " + speakerUrn);
//            return;
//        }

//        LOG.trace("generateSpeaksFor_Helper() has target info");

        PrivateKey userPrivateKey = geniUserProvider.getLoggedInGeniUser().getPrivateKey();

        Date expireDate =
                new Date(System.currentTimeMillis() + (expireInDays * 24 * 60 * 60 * 1000L)); //expireInDays days from now

        try {
            LOG.trace("generateSpeaksFor_Helper() creating credential");
            SfaCredential speakForCredential = SfaCredential.createSpeaksFor(
                    userInfo.getKey().getValue(), speakerUrn,
                    userInfo.getValue(), speakerCert,
                    userPrivateKey, expireDate, privilegeName, canDelegate);

            LOG.trace("generateSpeaksFor_Helper() showing result");
            setResult(speakForCredential.getCredentialXml());

            parameterHistoryModel.addUserCredential(speakForCredential);
        } catch (CredentialException e) {
            LOG.error("Exception creating speaks for credential", e);
            setResult("ERROR creating speaks for credential: " + e.getMessage());
        }
    }

    public void save() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Save Credential File");
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Credential XML Files", "*.xml"));
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All Files", "*.*"));
        File file = fileChooser.showSaveDialog(root.getScene().getWindow());
        if (file != null) {
            try {
                IOUtils.stringToFile(file, resultText.getText());
            } catch (Exception e) {
                LOG.error("Failed to save to file: " + file.getPath(), e);
            }
        }
    }
}
