# -*- coding: utf-8 -*-
import apollinaire as apn
import numpy as np
import pandas as pd
from astropy import units as u


def _resolve_selector(selector, default_index):
    selector = str(selector).strip()
    if selector == "":
        return default_index
    if selector.isdigit():
        return int(selector)
    return selector


def _extract_from_dataframe(df, selector, default_index, label):
    resolved = _resolve_selector(selector, default_index)
    if isinstance(resolved, int):
        try:
            return df.iloc[:, resolved].values
        except Exception as error:  # pylint: disable=broad-except
            raise ValueError(
                f"Invalid {label} column index '{resolved}'"
            ) from error

    if resolved not in df.columns:
        raise ValueError(
            f"Column '{resolved}' not found for {label}. Available columns: {list(df.columns)}"
        )
    return df[resolved].values


def _parse_header_array(header_value):
    if header_value is None:
        return np.array([])
    if isinstance(header_value, (list, tuple, np.ndarray)):
        return np.asarray(header_value, dtype=float).ravel()

    text = str(header_value).strip()
    if text == "":
        return np.array([])

    text = text.replace(",", " ")
    try:
        # Avoid np.fromstring deprecation by splitting and converting manually
        values = np.array([float(x) for x in text.split()], dtype=float)
        if values.size > 0:
            return values
    except ValueError:
        pass

    return np.array([])


def read_frequency_power(
    file_path,
    file_type="fits-image",
    frequency_selector="0",
    power_selector="1",
    fits_hdu="0",
    text_delimiter="auto",
    input_units=None,
    target_units=None,
):
    """Read frequency and power arrays from a configured source file.

    Supported file types are `fits-image`, `fits-table`, `csv`, and `txt`.
    Selectors can be numeric indices or column/header names.
    Includes automatic unit conversion for the frequency array.
    """
    file_type = str(file_type).strip().lower()

    if file_type in ["csv", "txt"]:
        delimiter_map = {
            "auto": None,
            "comma": ",",
            "tab": "\t",
            "space": "\\s+",
            "semicolon": ";",
            "pipe": "|",
        }
        sep = delimiter_map.get(str(text_delimiter).strip().lower(), None)
        data_frame = pd.read_csv(file_path, sep=sep, engine="python")
        ff = _extract_from_dataframe(
            data_frame, frequency_selector, 0, "frequency"
        )
        pp = _extract_from_dataframe(data_frame, power_selector, 1, "power")
    else:
        from astropy.io import fits

        with fits.open(file_path) as hdul:
            hdu_selector = str(fits_hdu).strip()
            if hdu_selector.isdigit():
                hdu = hdul[int(hdu_selector)]
            else:
                hdu = hdul[hdu_selector]

            data = hdu.data
            header = hdu.header

            if data is None:
                ff = _parse_header_array(
                    header.get(str(frequency_selector).strip())
                )
                pp = _parse_header_array(
                    header.get(str(power_selector).strip())
                )
            else:
                names = getattr(data, "names", None)
                if names:
                    data_frame = pd.DataFrame(data)
                    ff = _extract_from_dataframe(
                        data_frame, frequency_selector, 0, "frequency"
                    )
                    pp = _extract_from_dataframe(
                        data_frame, power_selector, 1, "power"
                    )
                else:
                    arr = np.asarray(data)
                    if arr.ndim < 2:
                        raise ValueError(
                            "FITS image data must be at least 2D for index-based selection."
                        )

                    freq_idx = _resolve_selector(frequency_selector, 0)
                    pwr_idx = _resolve_selector(power_selector, 1)
                    if not isinstance(freq_idx, int) or not isinstance(
                        pwr_idx, int
                    ):
                        raise ValueError(
                            "FITS image mode expects numeric frequency/power selectors."
                        )

                    if arr.shape[1] >= max(freq_idx, pwr_idx) + 1:
                        ff = arr[:, freq_idx]
                        pp = arr[:, pwr_idx]
                    elif arr.shape[0] >= max(freq_idx, pwr_idx) + 1:
                        ff = arr[freq_idx]
                        pp = arr[pwr_idx]
                    else:
                        raise ValueError(
                            "Could not map FITS image data to selected frequency/power indices."
                        )

    # Common post-processing: enforce numeric arrays and convert units
    ff = np.asarray(ff, dtype=float).ravel()
    pp = np.asarray(pp, dtype=float).ravel()

    if input_units and target_units and input_units != target_units:
        try:
            ff_q = ff * u.Unit(input_units)
            ff = ff_q.to(u.Unit(target_units)).value
        except Exception as e:
            raise ValueError(f"Frequencies unit conversion failed: {e}")

    return ff, pp


def inspect_source_file(
    file_path,
    file_type="fits-image",
    fits_hdu="0",
    text_delimiter="auto",
):
    """Return a short human-readable summary of source structure."""
    try:
        file_type = str(file_type).strip().lower()
        if file_type in ["csv", "txt"]:
            delimiter_map = {
                "auto": None,
                "comma": ",",
                "tab": "\t",
                "space": "\\s+",
                "semicolon": ";",
                "pipe": "|",
            }
            sep = delimiter_map.get(str(text_delimiter).strip().lower(), None)
            data_frame = pd.read_csv(file_path, sep=sep, engine="python")
            preview = list(data_frame.columns[:10])
            return (
                f"Type: {file_type} | Shape: {data_frame.shape} | "
                f"Columns (first 10): {preview}"
            )

        from astropy.io import fits

        with fits.open(file_path) as hdul:
            hdu_selector = str(fits_hdu).strip()
            if hdu_selector.isdigit():
                hdu = hdul[int(hdu_selector)]
            else:
                hdu = hdul[hdu_selector]

            data = hdu.data
            if data is None:
                return (
                    f"Type: {file_type} | HDU: {hdu_selector} | "
                    "Data: None (header-only)"
                )

            names = getattr(data, "names", None)
            if names:
                return (
                    f"Type: {file_type} | HDU: {hdu_selector} | "
                    f"Rows: {len(data)} | Columns: {list(names)[:10]}"
                )

            arr = np.asarray(data)
            return (
                f"Type: {file_type} | HDU: {hdu_selector} | "
                f"Array shape: {arr.shape}"
            )
    except Exception as error:  # pylint: disable=broad-except
        return f"Could not inspect source: {error}"


def calculate_synthetic_spectrum(
    fits_file=None, bkg_file=None, pkb_file=None, n_harvey=2, environment=None
):
    """
    From the filename it calculate synthetic spectrum
    """
    if environment is None:
        raise ValueError(
            "Environment instance must be provided to calculate_synthetic_spectrum"
        )

    param_back_raw = np.loadtxt(bkg_file)
    if param_back_raw.ndim > 1:
        param_back = param_back_raw[:, 0]
    else:
        param_back = param_back_raw

    pkb = np.loadtxt(pkb_file)
    ff, pp = read_frequency_power(
        file_path=fits_file,
        file_type=getattr(environment, "source_file_type", "fits-image"),
        frequency_selector=getattr(
            environment, "source_frequency_column", "0"
        ),
        power_selector=getattr(environment, "source_power_column", "1"),
        fits_hdu=getattr(environment, "source_fits_hdu", "0"),
        text_delimiter=getattr(environment, "source_text_delimiter", "auto"),
        input_units=getattr(environment, "source_input_frequency_unit", None),
        target_units=getattr(
            environment, "source_target_frequency_unit", None
        ),
    )
    ff = ff.byteswap().newbyteorder() if hasattr(ff, "newbyteorder") else ff
    pp = pp.byteswap().newbyteorder() if hasattr(pp, "newbyteorder") else pp

    f = (
        ff
        * u.Unit(getattr(environment, "source_target_frequency_unit", "uHz"))
    ).to(environment.frequency_unit)
    p = pp * environment.power_unit

    ff = f.value
    pp = p.value
    freq = ff
    noise_free, entropy = apn.synthetic.create_synthetic_psd(
        freq, pkb, param_back=param_back, noise_free=True, n_harvey=n_harvey
    )

    # psd, entropy = apn.synthetic.create_synthetic_psd (freq, pkb, param_back=param_back,
    #                                               entropy=127138838169534406638366956769226291439)

    value_dict = {}
    value_dict["synthetic_psd"] = noise_free
    value_dict["freq"] = freq
    value_dict["psd"] = pp
    data_frame = pd.DataFrame(value_dict)
    return data_frame
