import csv
import os

from java.lang import ProcessBuilder, String
from java.io import File, BufferedReader, InputStreamReader
from java.util import ArrayList
from ij import IJ


def build_args(inputDir, outputDir, envPython, modelChoice, customModel,
               variant, attenCap, useGpu, segChannel, quantChannel,
               poolingMode, referenceReplicate, sidecarScript, batchSize=8):
    """Build the argument list for the sidecar CLI; returns a Java ArrayList of strings.

    envPython is the full path to the Python interpreter in the cellpose4_p63 env.
    sidecarScript is the resolved absolute path to sidecar/run_p63.py (computed in
    p63_Cellpose_Batch.py where __file__ resolution is already correct).
    Paths are converted to absolute strings so spaces are handled correctly.
    """
    sidecar_script = str(sidecarScript)

    # model arg: named pretrained model or path to a custom model file
    if str(modelChoice) == "custom":
        model_arg = str(customModel)
    else:
        model_arg = str(modelChoice)   # "cpsam" or "cyto3"

    cmd = ArrayList()
    for token in [
        str(envPython),
        "-u",          # force line-buffered stdout so IJ.log sees lines as they print
        sidecar_script,
        "--input-dir",    str(inputDir),
        "--output-dir",   str(outputDir),
        "--model",        model_arg,
        "--seg-channel",  str(int(segChannel)),
        "--quant-channel", str(int(quantChannel)),
        "--variant",      str(variant),
        "--atten-cap",    "{:.2f}".format(float(attenCap)),
        "--pooling-mode", str(poolingMode),
    ]:
        cmd.add(String(token))

    cmd.add(String("--batch-size"))
    cmd.add(String(str(int(batchSize))))

    if useGpu:
        cmd.add(String("--use-gpu"))

    if str(poolingMode) == "reference_replicate":
        cmd.add(String("--reference-replicate"))
        cmd.add(String(str(referenceReplicate)))

    return cmd


def run_sidecar(args):
    """Launch the sidecar via ProcessBuilder, stream combined stdout+stderr to IJ.log; return the exit code.

    args should be the ArrayList returned by build_args.
    Raises RuntimeError on non-zero exit so the caller can surface a clear message.
    """
    pb = ProcessBuilder(args)
    pb.redirectErrorStream(True)   # merge stderr into stdout so we see tracebacks

    IJ.log("--- p63 sidecar starting ---")
    IJ.log("Command: " + " ".join(list(args)))

    process = pb.start()

    # Stream output line-by-line to IJ Log so errors are visible immediately
    reader = BufferedReader(InputStreamReader(process.getInputStream()))
    log_lines = []
    line = reader.readLine()
    while line is not None:
        IJ.log(line)
        log_lines.append(line)
        line = reader.readLine()

    exit_code = process.waitFor()
    IJ.log("--- p63 sidecar finished (exit={}) ---".format(exit_code))

    if exit_code != 0:
        tail = "\n".join(log_lines[-20:])   # last 20 lines capture the traceback
        raise RuntimeError(
            "Sidecar exited with code {}.\nLast output:\n{}".format(exit_code, tail)
        )

    return exit_code


def _parse_csv(path):
    """Read a CSV file into a list of dicts (one dict per row)."""
    rows = []
    with open(path, "r") as fh:
        reader = csv.DictReader(fh)
        for row in reader:
            rows.append(dict(row))
    return rows


def read_summary_csv(outputDir):
    """Parse summary.csv from outputDir; return a list of dicts, one per image."""
    return _parse_csv(os.path.join(str(outputDir), "summary.csv"))


def read_cells_csv(outputDir):
    """Parse cells.csv from outputDir; return a list of dicts, one per cell."""
    return _parse_csv(os.path.join(str(outputDir), "cells.csv"))
