import os
import numpy as np
import nibabel as nib
import pydicom
import nrrd
import cv2

from api.module import Module
from api.io_port import OutPort, NIFTIImageType


def load_nifti(path):
    nii = nib.load(path)
    return nii.get_fdata().astype(np.float32)


def load_dicom(path):
    if os.path.isdir(path):
        files = sorted([
            os.path.join(path, f)
            for f in os.listdir(path)
            if not f.startswith(".")
        ])
        slices = [pydicom.dcmread(f).pixel_array for f in files]
        return np.stack(slices).astype(np.float32)

    ds = pydicom.dcmread(path)
    return ds.pixel_array.astype(np.float32)


def load_nrrd(path):
    data, _ = nrrd.read(path)
    return data.astype(np.float32)


def load_numpy(path):
    return np.load(path).astype(np.float32)

def load_image(path):
    img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    if img is None:
        raise ValueError(f"Cannot read image file: {path}")
    return img.astype(np.float32)


def load_any(path):
    path = path.replace("\\", "/").lower()

    if path.endswith((".nii", ".nii.gz")):
        return load_nifti(path)

    if path.endswith(".nrrd"):
        return load_nrrd(path)

    if path.endswith(".npy"):
        return load_numpy(path)

    if path.endswith((".png", ".jpg", ".jpeg", ".bmp", ".tif", ".tiff")):
        return load_image(path)

    if os.path.isdir(path) or path.endswith(".dcm"):
        return load_dicom(path)

    raise ValueError(f"Unsupported image format: {path}")


def factory():
    m = Module("ImageReader")

    out_image = OutPort("image", NIFTIImageType(metadata={"role": "image"}))
    out_mask  = OutPort("mask",  NIFTIImageType(metadata={"role": "mask"}))

    m.add_out_port(out_image)
    m.add_out_port(out_mask)

    def execute(inputs):
        image_dir = "data/images"
        mask_dir  = "data/masks"

        image_files = [f for f in os.listdir(image_dir) if not f.startswith(".")]
        mask_files  = [f for f in os.listdir(mask_dir)  if not f.startswith(".")]

        if not image_files:
            raise ValueError("No image found in data/images")

        if not mask_files:
            raise ValueError("No mask found in data/masks")

        image_path = os.path.join(image_dir, image_files[0])
        mask_path  = os.path.join(mask_dir,  mask_files[0])

        try:
            image = load_any(image_path)
        except Exception as e:
            raise ValueError(f"Cannot read image: {image_path} ({e})")

        try:
            mask = load_any(mask_path)
        except Exception as e:
            raise ValueError(f"Cannot read mask: {mask_path} ({e})")

        return {
            "image": image,
            "mask":  mask
        }

    m.execute = execute
    return m
