from __future__ import annotations

import contextlib
import importlib.resources
import json
import logging
from io import BytesIO
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFont
from scipy.stats import norm

import yarrtist.utils.fonts

logger = logging.getLogger("YARRtist")


def get_chip_types_configs_from_scanlog(scanlog, scandir, relative_path=None):
    all_conns = []
    for conn in scanlog.get("connectivity"):
        chip_type = conn.get("chipType")
        all_configs = []
        for chip in conn.get("chips"):
            chip_path = Path(chip.get("__config_path__") or chip.get("config"))
            if relative_path:
                full_path = Path(relative_path) / Path(str(chip_path))
            else:
                full_path = Path(str(chip_path))
            if full_path.exists():
                logger.debug("Found configs from scanLog")
                all_configs.append(full_path)
            else:
                logger.debug(
                    "Config file not found from scanLog, trying to get it directly from scan folder (.before)"
                )
                before_cfg = str(chip_path).rsplit("/", maxsplit=1)[-1] + ".before"
                if Path(str(scandir) + "/" + before_cfg).exists():
                    all_configs.append(str(scandir) + "/" + before_cfg)
                else:
                    logger.warning(f"{before_cfg} not found, skipping")
        if len(all_configs) == 0:
            logger.warning(
                "No matching files between scanLog and files in folder, getting all .before indiscriminatly"
            )
            all_configs = [
                str(scandir) + "/" + f.name
                for f in Path(scandir).iterdir()
                if ".before" in f.name
            ]

        all_conns.append({"chipType": chip_type, "configs": list(set(all_configs))})

    return all_conns


def get_test_list_from_scandir(all_chip_names, scan_dir):
    all_tests = []

    for file_name in [
        f.name
        for f in Path(scan_dir).iterdir()
        if f.is_file()
        and ".before" not in f.name
        and ".after" not in f.name
        and ".json" in f.name
    ]:
        for chip in all_chip_names:
            if chip in file_name:
                extra = file_name.replace(chip + "_", "")
                all_tests.append(extra.split(".")[0])
    return list(set(all_tests))


def load_data(input_data):
    with Path(input_data).open() as file:
        return json.load(file)


def get_chip_type_from_config(config):
    chiptype = None
    try:
        chiptype = next(iter(config.keys()))
    except IndexError:
        logger.error("One of your chip configuration files is empty")

    return chiptype


def get_geomId_sn_from_config(config_data, chip_type=None):
    if not chip_type:
        chip_type = get_chip_type_from_config(config_data)

    if chip_type == "FEI4B":
        chip_name = config_data["FE-I4B"]["name"]
    elif chip_type in ["RD53A", "RD53B", "ITKPIXV2"]:
        chip_type = get_chip_type_from_config(config_data)
        chip_name = config_data[chip_type]["Parameter"]["Name"]
    elif chip_type == "FE65-P2":
        chip_name = config_data[chip_type]["name"]
    elif chip_type == "Star":
        chip_name = config_data["name"]
    else:
        logger.error("Unknown chip type")
        return "", ""

    if chip_type in ["RD53B", "ITKPIXV2"]:
        chip_id = config_data[chip_type]["Parameter"]["ChipId"]
    else:
        chip_id = 0

    if chip_id == 12:
        return 1, chip_name
    if chip_id == 13:
        return 2, chip_name
    if chip_id == 14:
        return 3, chip_name
    if chip_id == 15:
        return 4, chip_name
    return chip_id, chip_name


def format_broken_data(plot, test, ctype, geomId):
    if "InjVcalDiff_Map" in test:
        plot["data"].update(
            {
                geomId: np.zeros((153600, 101))
                if ctype == "ITKPIXV2"
                else np.zeros((153600, 61))
            }
        )
    elif "InjVcalDiff" in test:
        plot["data"].update(
            {geomId: np.zeros((101, 49)) if ctype == "ITKPIXV2" else np.zeros((61, 49))}
        )
    else:
        plot["data"].update({geomId: np.zeros((400, 384))})


def format_data(plot, data, geomId):
    plot["type"] = data.get("Type")
    plot["title"] = data.get("Name")

    if plot.get("type") == "Histo2d":
        data_arr = np.array(data.get("Data"))
        if None in data_arr:
            data_arr[np.equal(data_arr, None)] = 0
        plot["data"].update({geomId: data_arr})
    else:
        plot["data"].update({geomId: data})


def fill_pdf_summary(plotter, all_imgs, dpi=300):
    buf = BytesIO()
    plotter.savefig(buf, format="png", dpi=dpi)
    all_imgs.append(buf)
    buf.seek(0)
    try:
        plotter.close()
    except Exception:
        contextlib.suppress(Exception)


def create_img_grid(images_buf, grid, text=None):
    spacing = 10
    bkg_color = (255, 255, 255)

    images = [Image.open(p) for p in images_buf]

    max_width = max(img.width for img in images)
    max_height = max(img.height for img in images)

    rows, cols = grid
    total_width = cols * max_width + (cols - 1) * spacing
    total_height = rows * max_height + (rows - 1) * spacing

    text_height = 0
    if text is not None:
        try:
            try:
                with (
                    importlib.resources.files(yarrtist.utils.fonts)
                    .joinpath("Roboto_Condensed-BoldItalic.ttf")
                    .open("rb")
                ) as f:
                    font = ImageFont.truetype(f, 65)
            except Exception:
                with importlib.resources.path(  # pylint: disable=deprecated-method
                    "yarrtist.utils.fonts", "Roboto_Condensed-BoldItalic.ttf"
                ) as font_path:
                    font = ImageFont.truetype(str(font_path), 65)
        except OSError as e:
            logger.debug(e)
            font = ImageFont.load_default()
        bbox = font.getbbox(text)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        total_height += text_height + spacing

    combined_image = Image.new("RGB", (total_width, total_height), bkg_color)

    for idx, img in enumerate(images):
        row, col = divmod(idx, cols)
        x = col * (max_width + spacing)
        y = row * (max_height + spacing)
        combined_image.paste(img, (x, y))

    if text is not None:
        draw = ImageDraw.Draw(combined_image)
        text_x = (total_width - text_width) // 2
        text_y = total_height - text_height - 150 * spacing
        draw.text((text_x, text_y), text, fill="black", font=font)

    return combined_image


def get_configs_from_connectivity(conn_data, config_dir, relative_path=None):
    configs = []

    if not relative_path:
        relative_path = ""

    chip_type = conn_data.get("chipType")
    for c in conn_data.get("chips", []):
        if c.get("path") == "relToCon":
            configs.append(Path(config_dir) / Path(c.get("config")))
        elif c.get("path") == "abs":
            configs.append(Path(c.get("config")))
        elif c.get("path") == "relToExec" or c.get("path") == "relToYarrPath":
            configs.append(Path(relative_path) / Path(c.get("config")))
        else:
            logger.warning("Assuming that YARRtist is called from the right folder.")
            configs.append(Path(c.get("config")))

    return {"chipType": chip_type, "configs": list(set(configs))}


def data_from_config(config_data):
    plot_data = []

    chip_type = get_chip_type_from_config(config_data)
    config_pixel = config_data[chip_type].get("PixelConfig", []) if chip_type else []

    if config_pixel:
        pixelDataMap = {
            k: [dic[k] for dic in config_pixel] for k in config_pixel[0] if k != "Col"
        }

        for key, data in pixelDataMap.items():
            plot_data.append(
                {
                    "Data": data,
                    "Entries": np.sum(np.array(data)),
                    "Name": key + "-Map",
                    "Overflow": 0.0,
                    "Type": "Histo2d",
                    "Underflow": 0.0,
                    "x": {
                        "AxisTitle": "Column",
                        "Bins": 400,
                        "High": 400.5,
                        "Low": 0.5,
                    },
                    "y": {"AxisTitle": "Row", "Bins": 384, "High": 384.5, "Low": 0.5},
                    "z": {"AxisTitle": key},
                }
            )

            if key == "TDAC":
                flat = np.array(data).flatten()
                bins = np.linspace(-15.5, 15.5, 32)
                hist, _edges = np.histogram(flat, bins=bins)
                plot_data.append(
                    {
                        "Data": hist,
                        "Entries": np.sum(flat),
                        "Name": "TADC-Dist",
                        "Overflow": 0.0,
                        "Type": "Histo1d",
                        "Underflow": 0.0,
                        "x": {
                            "AxisTitle": "TDAC",
                            "Bins": 31,
                            "High": 15.5,
                            "Low": -15.5,
                        },
                        "y": {"AxisTitle": "Number of Pixels"},
                        "z": {"AxisTitle": "z"},
                    }
                )

    else:
        logger.debug("No data for chip")
        for key in ["Enable", "Hitbus", "InjEn", "TDAC"]:
            plot_data.append(
                {
                    "Data": np.zeros((400, 384)),
                    "Entries": 0,
                    "Name": key + "-Map",
                    "Overflow": 0.0,
                    "Type": "Histo2d",
                    "Underflow": 0.0,
                    "x": {
                        "AxisTitle": "Column",
                        "Bins": 400,
                        "High": 400.5,
                        "Low": 0.5,
                    },
                    "y": {"AxisTitle": "Row", "Bins": 384, "High": 384.5, "Low": 0.5},
                    "z": {"AxisTitle": key},
                }
            )

    return plot_data


def fit_Histo1d(freq, bins):
    centers = []
    for i in range(len(bins)):
        centers.append(0.5 * (bins[i] + bins[i - 1]))

    data = np.repeat(centers, freq)

    return norm.fit(data)
