from __future__ import annotations

import contextlib
import logging
import os
import warnings
from datetime import datetime, timezone
from functools import cache
from typing import Any

import jpype
import scyjava
import scyjava.config
from jpype.types import JString

MAVEN_COORDINATE = "ome:formats-gpl:RELEASE"

# Configure Java constraints from environment variables
# BFF_JAVA_VENDOR: Java vendor (e.g., "zulu-jre", "adoptium", "temurin")
# BFF_JAVA_VERSION: Java version (e.g., "11", "17", "21")
# BFF_JAVA_FETCH: Fetch mode ("always", "never", "auto", default is "always")
_bff_vendor = os.getenv("BFF_JAVA_VENDOR") or None
_bff_version = os.getenv("BFF_JAVA_VERSION") or None
_bff_fetch = os.getenv("BFF_JAVA_FETCH") or None
if _bff_vendor or _bff_version:
    _kwargs = {}
    if _bff_vendor:
        _kwargs["vendor"] = _bff_vendor
    if _bff_version:
        _kwargs["version"] = _bff_version
    # Control fetch behavior via environment variable
    # Default: don't force download unless explicitly requested
    if _bff_fetch:
        _kwargs["fetch"] = _bff_fetch
    scyjava.config.set_java_constraints(**_kwargs)

# Check if the BIOFORMATS_VERSION environment variable is set
# and if so, use it as the Maven coordinate
if coord := os.getenv("BIOFORMATS_VERSION", ""):
    # allow a single version number to be passed
    if ":" not in coord and all(x.isdigit() for x in coord.split(".")):
        # if the coordinate is just a version number, use the default group and artifact
        coord = f"ome:formats-gpl:{coord}"

    # ensure the coordinate is valid
    if 2 > len(coord.split(":")) > 5:
        warnings.warn(
            f"Invalid BIOFORMATS_VERSION env var: {coord!r}. "
            "Must be a valid maven coordinate with 2-5 elements. "
            f"Using default {MAVEN_COORDINATE!r}",
            stacklevel=2,
        )
    else:
        MAVEN_COORDINATE = coord

scyjava.config.endpoints.append(MAVEN_COORDINATE)
# NB: logback 1.3.x is the last version with Java 8 support!
scyjava.config.endpoints.append("ch.qos.logback:logback-classic:1.3.15")

# #################################### LOGGING ####################################

# python-side logger

LOGGER = logging.getLogger("bffile")
fmt = (
    "%(asctime)s.%(msecs)03d "  # timestamp with milliseconds
    "[%(levelname)-5s] "  # level, padded
    "%(name)s:%(lineno)d - "  # logger name and line no.
    "%(message)s"  # the log message
)
datefmt = "%Y-%m-%d %H:%M:%S"
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(fmt=fmt, datefmt=datefmt))
# avoid double-logs if somebody has already attached handlers
if not any(isinstance(h, logging.StreamHandler) for h in LOGGER.handlers):
    LOGGER.addHandler(handler)


def redirect_java_logging(logger: logging.Logger | None = None) -> None:
    """Redirect Java logging to Python logger."""
    _logger = logger or LOGGER

    class PyAppender:
        def doAppend(self, event: Any) -> None:
            # event is an ILoggingEvent
            msg = str(event.getFormattedMessage())
            level = str(event.getLevel())
            # dispatch to Python logger
            getattr(logger, level.lower(), _logger.info)(msg)

        def getName(self) -> str:
            return "PyAppender"

    # Create a proxy for the Appender interface
    proxy = jpype.JProxy("ch.qos.logback.core.Appender", inst=PyAppender())

    # Get the LoggerContext
    Slf4jFactory = scyjava.jimport("org.slf4j.LoggerFactory")
    root = Slf4jFactory.getILoggerFactory().getLogger("ROOT")

    # remove the console appender
    with contextlib.suppress(AttributeError):
        for appender in root.iteratorForAppenders():
            if appender.getName() in ("console", "PyAppender"):
                root.detachAppender(appender)

        # add the Python appender
        root.addAppender(proxy)


@cache  # run only once
def start_jvm() -> None:
    """Start the JVM if not already running."""
    scyjava.start_jvm()  # won't repeat if already running
    redirect_java_logging()


def jtype_to_python(obj: Any) -> Any:
    """Convert a Java type to a native Python type if possible."""
    if isinstance(obj, JString):
        return str(obj)
    if isinstance(obj, int):
        return int(obj)
    if isinstance(obj, float):
        return float(obj)
    if isinstance(obj, bool):
        return bool(obj)
    if hasattr(obj, "to_pint"):
        return obj.to_pint()
    if hasattr(obj, "asInstant"):
        # ome.xml.model.primitives.Timestamp -> datetime
        return datetime.fromisoformat(str(obj.getValue())).replace(tzinfo=timezone.utc)
    if isinstance(obj, jpype.JClass("java.util.Map")):
        return {jtype_to_python(k): jtype_to_python(v) for k, v in obj.items()}
    if isinstance(obj, jpype.JClass("java.util.Collection")):
        return [jtype_to_python(item) for item in obj]
    # Fallback: convert any remaining Java object to string
    if hasattr(obj, "toString"):
        return str(obj)
    return obj
