package be.iminds.ilabt.jfed.experimenter_gui.bugreport_viewer;

import be.iminds.ilabt.jfed.bugreport.*;
import be.iminds.ilabt.jfed.call_log_output.SerializableApiCallDetails;
import be.iminds.ilabt.jfed.experimenter_gui.bugreporting.*;
import be.iminds.ilabt.jfed.log.cache.ApiCallDetailsCache;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.ui.javafx.log_gui.LogHistoryPanel;
import be.iminds.ilabt.jfed.util.IOUtils;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.util.StringConverter;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.Collections;
import java.util.List;

/**
 * BugReportViewerPanel
 */
public class BugReportViewerPanel {
    private static final Logger LOG = LoggerFactory.getLogger(BugReportViewerPanel.class);
    public static final String BUG_REPORT_VIEWER_FXML = "BugReportViewerPanel.fxml";

    @FXML
    private TextField reportChooserTextField;

    @FXML
    private TextArea descriptionTextArea;
    @FXML
    private TextField versionTextField;
    @FXML
    private TextField environmentTextField;
    @FXML
    private TextField reporterUrnTextField;
    @FXML
    private TextField emailTextField;
    @FXML
    private Label includedCallsLabel;

    @FXML
    private TextArea loggedInUserTextArea;
    @FXML
    private TextArea preferencesTextArea;
    @FXML
    private TextArea slicesTextArea;

    @FXML
    private LogHistoryPanel logHistoryPanel;

    @FXML
    private CheckBox showThreadOverviewCheckBox;

    @FXML
    private ChoiceBox<LogView.Level> filterLevelChoicebox;

    @FXML
    private CheckBox showTimestamp;
    @FXML
    private CheckBox showThreadname;
    @FXML
    private CheckBox showLevel;


    @FXML
    private LogView debugLogs;
    private FilteredList<LogLine> filteredLogLines;
    @FXML
    private TextField searchDebugField;
    @FXML
    private ConnectivityTestTable connectivityTestTable;

    @FXML
    private ComboBox<StitchingJobReport> stitchingReportComboBox;
    @FXML
    private WebView stitchingReportWebView;

    @FXML
    private ImageView screenshotImageView;
    @FXML
    private Tab screenshotTab;


    private final ApiCallDetailsCache cache;

    @Inject
    public BugReportViewerPanel(ApiCallDetailsCache cache) {
        this.cache = cache;
    }

    @FXML
    private void initialize() {
        LOG.debug("Bug Report Viewer initialize");

        logHistoryPanel.setAutoShowLast(false);
        logHistoryPanel.setAuthShowLastVisible(false);

        if (startReport != null)
            try {
                fetchReport(startReport);
            } catch (IOException e) {
                LOG.error("Failed to fetch startReport \"" + startReport + "\"", e);
            }

        stitchingReportComboBox.setCellFactory(TextFieldListCell.forListView(new StringConverter<StitchingJobReport>() {
            @Override
            public String toString(StitchingJobReport r) {
                if (r == null) return "";
                if (r.getStitchingJobStateReports().isEmpty()) return "empty StitchingReport";
                return "StitchingReport @ " + r.getStitchingJobStateReports().get(0).getStart();
            }

            @Override
            public StitchingJobReport fromString(String s) {
                throw new RuntimeException("not needed");
            }
        }));

        stitchingReportComboBox.setOnAction(event -> {
            StitchingJobReport r = stitchingReportComboBox.getSelectionModel().getSelectedItem();
            if (r == null) {
                WebEngine webEngine = stitchingReportWebView.getEngine();
                webEngine.loadContent("<html><body><p>No Report</p></body></html>");
            } else {
                SerializableStitchingJobReportWriter writer = new SerializableStitchingJobReportWriter(r, cache);
                String html = writer.toHtmlString(false);

                WebEngine webEngine = stitchingReportWebView.getEngine();
                webEngine.loadContent(html);
            }
        });

        debugLogs.showTimestampProperty().bind(showTimestamp.selectedProperty());
        debugLogs.showThreadnameProperty().bind(showThreadname.selectedProperty());
        debugLogs.showLevelProperty().bind(showLevel.selectedProperty());

        filterLevelChoicebox.getItems().setAll(LogView.Level.values());
        filterLevelChoicebox.getSelectionModel().selectFirst();

        filterLevelChoicebox.getSelectionModel().selectedItemProperty().addListener(observable -> updateDebugLogFilters());
        searchDebugField.textProperty().addListener(event -> updateDebugLogFilters());
        showThreadOverviewCheckBox.selectedProperty().addListener(observable -> updateDebugLogFilters());

    }


    private void updateDebugLogFilters() {
        final String searchString = searchDebugField.getText().trim().isEmpty() ?
                null : searchDebugField.getText().toLowerCase();

        filteredLogLines.setPredicate(logLine -> {
            if (logLine == null)
                return false;

            if (!filterLevelChoicebox.getSelectionModel().isEmpty()
                    && LogView.Level.valueOf(logLine.getLevel()).ordinal() <
                    filterLevelChoicebox.getSelectionModel().getSelectedItem().ordinal())
                return false;

            //noinspection RedundantIfStatement
            if (!showThreadOverviewCheckBox.isSelected() && logLine.getMessage() != null && logLine.getMessage().startsWith("Thread Overview"))
                return false;

            return searchString == null ||
                    logLine.getClassName() != null && logLine.getClassName().toLowerCase().contains(searchString)
                    || logLine.getMessage() != null && logLine.getMessage().toLowerCase().contains(searchString);
        });
    }


    private String startReport;

    public void setStartReport(String urlString) throws IOException {
        this.startReport = urlString;

        if (reportChooserTextField != null)
            fetchReport(startReport);
    }

    public void fetchReport(String urlString) throws IOException {
        reportChooserTextField.setText(urlString);
        URL testUrl = new URL(urlString);
        String bugReportContent = IOUtils.streamToString(testUrl.openStream(), "UTF-8");

        showReport(bugReportContent);
    }

    @FXML
    public void fetchReport() throws IOException {
        URL testUrl = new URL(reportChooserTextField.getText());
        String bugReportContent = IOUtils.streamToString(testUrl.openStream(), "UTF-8");

        showReport(bugReportContent);
    }

    @FXML
    public void openReport() throws IOException {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Open BugReport file");
        File file = fileChooser.showOpenDialog(reportChooserTextField.getScene().getWindow());
        URL fileUrl = file.toURI().toURL();

        reportChooserTextField.setText(fileUrl + "");
        fetchReport();
    }

    @FXML
    public void showReport(String bugReportContent) throws IOException {

        BugReportReader bugReportReader =
                new BugReportReader(new StringReader(bugReportContent), cache);

        BugReport bugReport;
        try {
            bugReport = bugReportReader.call();
        } catch (Exception e) {
            LOG.error("Error while reading bugreport", e);
            JFDialogs.create().message("Could not parse bugreport").showException(e);
            return;
        }
        List<SerializableApiCallDetails> apiCallDetails = Collections.emptyList(); //bugReport.getCalls(); todo: fetch calls
        // for (SerializableApiCallDetails apiCallDetail : apiCallDetails)
        //apiCallDetail.reconstructApiCallReply(); //HACK, because reply is not in XML but can be partially recreated

        if (apiCallDetails != null) {
            ObservableList<SerializableApiCallDetails> observableApiCallDetails =
                    FXCollections.observableArrayList(apiCallDetails);
            logHistoryPanel.setApiCallHistory(observableApiCallDetails);
        }

        descriptionTextArea.setText(bugReport.getDescription());
        versionTextField.setText(bugReport.getVersion());
        environmentTextField.setText(bugReport.getEnvironment());
        reporterUrnTextField.setText(bugReport.getReporterUrn());
        emailTextField.setText(bugReport.getMail());
        includedCallsLabel.setText(bugReport.getApiCallDetailIds().size() + "");

        StringBuilder sliceText = new StringBuilder();
        if (bugReport.getSlices() != null) {
            for (Slice slice : bugReport.getSlices()) {
                sliceText.append("Slice \"").append(slice.getSliceUrn()).append("\"").append("\n");
                if (slice.getStatus() != null)
                    sliceText.append("\tStatus: ").append(slice.getStatus()).append("\n");
                if (slice.getSlivers() != null)
                    for (Sliver sliver : slice.getSlivers()) {
                        sliceText.append("   Sliver \"").append(sliver.getSliverUrn()).append("\"").append("\n");
                        sliceText.append("       sliverStatus=").append(sliver.getSliverStatus()).append("").append("\n");
                        sliceText.append("       currentStatus=").append(sliver.getCurrentStatus()).append("").append("\n");
                        if (sliver.getComponents() != null && !sliver.getComponents().isEmpty())
                            sliceText.append("       component(s)=").append(sliver.getComponents()).append("").append("\n");
//                    if (sliver.getNodesConnectivity() != null)
//                        for (Map.Entry<String, ConnectivityTest.Status> e : sliver.getNodesConnectivity().entrySet())
//                            sliceText.append("       node \""+e.getKey()+"\" status="+e.getValue()+"\n");
                    }
                sliceText.append("\n");
            }
        }
        slicesTextArea.setText(sliceText.toString());
        loggedInUserTextArea.setText(bugReport.getLoggedInUserInfo());
        preferencesTextArea.setText(bugReport.getPreferences());

        List<LogLine> logLines = bugReport.getLogLines();
        if (logLines != null) {
            filteredLogLines = new FilteredList<>(FXCollections.observableArrayList(logLines));
            debugLogs.setItems(filteredLogLines);
        } else {
            filteredLogLines = new FilteredList<>(FXCollections.observableArrayList(Collections.emptyList()));
        }

        updateDebugLogFilters();

        List<ConnectivityTestResult> connectivityTests = bugReport.getConnectivityTestResults();
        if (connectivityTests != null)
            connectivityTestTable.setItems(FXCollections.observableArrayList(connectivityTests));

        if (bugReport.getScreenshot() != null) {
            byte[] decodedImage = Base64.decodeBase64(bugReport.getScreenshot());

            if (decodedImage != null) {
                ByteArrayInputStream imageInputStream = new ByteArrayInputStream(decodedImage);
                Image image = new Image(imageInputStream);

                screenshotImageView.setImage(image);
            } else {
                screenshotImageView.setImage(null);
            }
        } else {
            screenshotImageView.setImage(null);
        }

        screenshotTab.setDisable(screenshotImageView.getImage() == null);


        assert stitchingReportComboBox != null;
        assert bugReport.getStitchingJobReports() != null;
        stitchingReportComboBox.setItems(FXCollections.observableArrayList(bugReport.getStitchingJobReports()));

    }
}
