/*
 * Copyright (C) 2025-2026  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.ui.tab.species;

import org.aglais.zms.AglaisZMS;
import org.aglais.zms.db.core.model.ByteArrayBlob;
import org.aglais.zms.db.core.model.ModifiedStateTracker;
import org.aglais.zms.db.core.model.Species;
import org.aglais.zms.gbif.GBIFAPI;
import org.aglais.zms.gbif.GBIFSpeciesIUCNRedListCategory;
import org.aglais.zms.gbif.GBIFSpeciesMatch;
import org.aglais.zms.ui.component.DangerButton;
import org.aglais.zms.ui.component.IUCNStatus;
import org.aglais.zms.ui.component.ImagePanel;
import org.aglais.zms.ui.tab.ISavableTab;
import org.aglais.zms.ui.tab.SubTab;
import org.aglais.zms.ui.tab.SpeciesTab;
import org.aglais.zms.i18n.InterpolatingResourceBundle;
import org.aglais.zms.util.Log;
import org.jetbrains.annotations.NotNull;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ResourceBundle;

/**
 * Tab for displaying and editing basic species information like scientific name, common name,
 * origin, notes and an optional species image. Also shows the IUCN conservation status
 * by querying the GBIF API.
 */
public class SpeciesInfoTab extends SubTab<SpeciesTab> implements ISavableTab {

    private static final String LOG_TAG = SpeciesInfoTab.class.getSimpleName();

    private JPanel contentPane;
    private DangerButton deleteButton;
    private JButton saveButton;
    private JTextField scientificNameTextField;
    private JTextField commonNameTextField;
    private JTextField originTextField;
    private JTextArea notesTextArea;
    private ImagePanel speciesImage;
    private JButton chooseImageButton;
    private DangerButton clearImageButton;
    private IUCNStatus iucnStatus;
    private ModifiedStateTracker<Species> speciesModifiedTracker;

    /**
     * Creates a new species info tab.
     *
     * @param zms    The main application instance
     * @param parent The parent species tab
     */
    public SpeciesInfoTab(AglaisZMS zms, SpeciesTab parent) {
        super(zms, parent);

        InterpolatingResourceBundle uiBundle = (InterpolatingResourceBundle) ResourceBundle.getBundle("ui");

        deleteButton.addActionListener((event) -> {
            Species species = speciesModifiedTracker.getData();

            int result = JOptionPane.showConfirmDialog(contentPane,
                    uiBundle.getString("confirm_delete_species", species.getScientificName()),
                    uiBundle.getString("dialog_confirm_delete_species_title", species.getScientificName()),
                    JOptionPane.YES_NO_OPTION);

            if (result == JOptionPane.YES_OPTION) {
                if (zms.getClient().deleteSpecies(species)) {
                    speciesModifiedTracker.discardChanges();
                    parent.show();
                } else {
                    JOptionPane.showMessageDialog(contentPane, uiBundle.getString("error_delete_failed",
                                    species.getScientificName()), uiBundle.getString("error"),
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        });
        saveButton.addActionListener((event) -> saveChangesWithUI());

        // Editable text fields
        scientificNameTextField.addCaretListener((event) -> {
            Species species = speciesModifiedTracker.getData();
            species.setScientificName(scientificNameTextField.getText());
            updateModifiedState();
        });
        commonNameTextField.addCaretListener((event) -> {
            Species species = speciesModifiedTracker.getData();
            species.setCommonName(commonNameTextField.getText());
            updateModifiedState();
        });
        originTextField.addCaretListener((event) -> {
            Species species = speciesModifiedTracker.getData();
            species.setOrigin(originTextField.getText());
            updateModifiedState();
        });
        notesTextArea.addCaretListener((event) -> {
            Species species = speciesModifiedTracker.getData();
            species.setNotes(notesTextArea.getText());
            updateModifiedState();
        });

        chooseImageButton.addActionListener((event) -> {
            Species species = speciesModifiedTracker.getData() ;
            JFileChooser fileChooser = new JFileChooser();

            // Restrict to image files
            fileChooser.setAcceptAllFileFilterUsed(false);
            fileChooser.setFileFilter(new FileNameExtensionFilter(uiBundle.getString("images"),
                    ImageIO.getReaderFileSuffixes()));

            int result = fileChooser.showOpenDialog(contentPane);
            if (result == JFileChooser.APPROVE_OPTION) {
                File file = fileChooser.getSelectedFile();
                try {
                    Blob blob = new ByteArrayBlob(Files.readAllBytes(file.toPath()));
                    species.setImage(blob);
                    updateModifiedState();
                    updateImage();
                } catch (IOException e) {
                    Log.e(LOG_TAG, "Failed to read species image", e);

                    JOptionPane.showMessageDialog(contentPane, uiBundle.getString("error_read_image_failed"),
                            uiBundle.getString("error"), JOptionPane.ERROR_MESSAGE);
                }
            }
        });
        clearImageButton.addActionListener((event) -> {
            Species species = speciesModifiedTracker.getData();
            species.setImage(null);
            updateModifiedState();
            updateImage();
        });
    }

    /**
     * Updates the enabled state of the save button based on whether the species has been modified.
     */
    private void updateModifiedState() {
        saveButton.setEnabled(speciesModifiedTracker.isModified());
    }

    /**
     * Updates the displayed species image and enables/disables the clear button accordingly.
     */
    private void updateImage() {
        Species species = speciesModifiedTracker.getData();

        speciesImage.setImage(null);
        clearImageButton.setEnabled(species.getImage() != null);
        if (species.getImage() != null) {
            try {
                BufferedImage image = ImageIO.read(species.getImage().getBinaryStream());

                speciesImage.setImage(image);
            } catch (IOException | SQLException e) {
                Log.e(LOG_TAG, "Failed to load species image", e);
            }
        }
    }

    @Override
    public void onShow() {
        contentPane.getRootPane().setDefaultButton(saveButton);
        speciesModifiedTracker = new ModifiedStateTracker<>(parent.getSelectedSpecies());

        // Fill text fields with selected values
        Species species = speciesModifiedTracker.getData();
        scientificNameTextField.setText(species.getScientificName());
        commonNameTextField.setText(species.getCommonName());
        originTextField.setText(species.getOrigin());
        notesTextArea.setText(species.getNotes());
        notesTextArea.setCaretPosition(0);

        updateImage();
        updateModifiedState();

        iucnStatus.setStatus(null);
        iucnStatus.setInvisible(true);
    }

    @Override
    public void onShowAsync() {
        Species species = speciesModifiedTracker.getData();
        String scientificName = species.getScientificName();
        try {
            GBIFSpeciesMatch match = GBIFAPI.matchSpecies(scientificName);
            if (match != null) {
                GBIFSpeciesIUCNRedListCategory redListCategory = GBIFAPI.getSpeciesIUCNRedListCategory(match.usageKey());
                if (redListCategory != null) {
                    IUCNStatus.Status status = IUCNStatus.Status.valueOf(redListCategory.code().toUpperCase());

                    SwingUtilities.invokeLater(() -> {
                        iucnStatus.setStatus(status);
                        iucnStatus.setInvisible(!status.isDisplayable());
                    });
                }
            } else {
                Log.w(LOG_TAG, "Species not found in GBIF Database: " + scientificName);
            }
        } catch (Exception e) {
            Log.e(LOG_TAG, "Failed to query species: " + scientificName, e);
        }
    }

    @Override
    public @NotNull JComponent getContentPane() {
        return contentPane;
    }

    @Override
    public String getSaveTitle() {
        return speciesModifiedTracker != null ? speciesModifiedTracker.getData().getScientificName() : null;
    }

    @Override
    public boolean hasUnsavedChanges() {
        return speciesModifiedTracker != null && speciesModifiedTracker.isModified();
    }

    @Override
    public boolean saveChanges() {
        if (zms.getClient().updateSpecies(speciesModifiedTracker.getData())) {
            speciesModifiedTracker.applyChanges();
            updateModifiedState();
            parent.show();

            return true;
        }

        return false;
    }

    @Override
    public void discardChanges() {
        speciesModifiedTracker.discardChanges();
    }
}
