import os
import json
import hashlib
from IPython.display import IFrame, display as _ipy_display, HTML

from .download import get_wsi_binary, get_xopat_binary
from .wsi import start_wsi_service
from .xopat import start_xopat
from .layers import Layer, Mask, build_visualization_config, build_post_html, build_simple_url
from .shaders import heatmap, highlight, classify, bipolar, edge, identity


def configure(jupyterhub_host):
    """
    Configure xopat_deploy for JupyterHub environment.
    Call this before run_server() when running on JupyterHub.

    Args:
        jupyterhub_host: Full URL of JupyterHub, e.g. 'https://hub.example.com'
    """
    host = jupyterhub_host.rstrip("/")
    prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "").rstrip("/")
    if not prefix:
        raise RuntimeError("JUPYTERHUB_SERVICE_PREFIX not set - are you on JupyterHub?")

    wsi_path = f"{prefix}/proxy/8080"
    xopat_path = f"{prefix}/proxy/9000"

    config = {
        "core": {
            "gateway": "/",
            "active_client": "jupyter",
            "client": {
                "jupyter": {
                    "domain": host,
                    "path": xopat_path,
                    "image_group_server": host,
                    "image_group_protocol": f"`{wsi_path}/v3/slides/info?slide_id=${{data}}`",
                    "image_group_preview": f"`{wsi_path}/v3/slides/thumbnail/max_size/250/250?slide_id=${{data}}`",
                    "data_group_server": host,
                    "data_group_protocol": f"`{wsi_path}/v3/slides/info?slide_id=${{data}}`",
                    "headers": {},
                    "js_cookie_expire": 365,
                    "js_cookie_path": "/",
                    "js_cookie_same_site": "",
                    "js_cookie_secure": "",
                    "secureMode": False
                }
            },
            "setup": {"locale": "en", "theme": "auto"}
        },
        "plugins": {
            "slide-info": {"permaLoad": True},
            "file-browser": {"permaLoad": True}
        },
        "modules": {
            "empaia-wsi-tile-source": {"permaLoad": True},
            "mlflow": {"enabled": False}
        }
    }

    xopat_binary = get_xopat_binary()
    env_path = xopat_binary.parent / "xopat_env.json"
    env_path.write_text(json.dumps(config, indent=2))
    os.environ["XOPAT_ENV"] = str(env_path)
    print(f"Configured for JupyterHub: {host}{xopat_path}")


class Server:
    def __init__(self, wsi, xopat):
        self._wsi = wsi
        self._xopat = xopat
        self.wsi_url = wsi.base_url
        self.xopat_url = xopat.base_url

    def stop(self):
        try:
            self._xopat.stop()
        finally:
            self._wsi.stop()


def run_server(data_dir=None):
    wsi_binary = get_wsi_binary()
    xopat_binary = get_xopat_binary()
    wsi = start_wsi_service(wsi_binary, data_dir=data_dir)
    try:
        xopat = start_xopat(xopat_binary)
    except Exception:
        wsi.stop()
        raise
    return Server(wsi, xopat)


def display(server, slide_or_layer, masks=None, width="100%", height=800):
    """
    Display a slide (with optional mask overlays) in Jupyter.

    Usage:
        # Simple - just a slide
        display(server, "slide.tif")

        # Slide with masks (default shader)
        display(server, "slide.tif", masks=["mask.tif"])

        # Slide with masks and custom shaders
        from xopat_deploy.shaders import heatmap, classify
        display(server, "slide.tif", masks=[
            Mask("mask.tif", shader=heatmap(color="#ff0000")),
            Mask("classes.tif", shader=classify(classes=3)),
        ])

        # Full control via Layer object
        layer = Layer("slide.tif")
        layer.add_mask("mask.tif", shader=heatmap(color="#ff0000"))
        display(server, layer)

    Args:
        server: Server object from run_server()
        slide_or_layer: Path string to slide, or Layer object
        masks: Optional list of mask paths or Mask objects (ignored if Layer given)
        width: IFrame width
        height: IFrame height
    """
    if isinstance(slide_or_layer, Layer):
        layer = slide_or_layer
        config = build_visualization_config(layer.slide, layer.masks, layer.name)
        html = build_post_html(config, server.xopat_url, width, height)
        _ipy_display(HTML(html))
    elif isinstance(slide_or_layer, str):
        slide = slide_or_layer
        if masks:
            config = build_visualization_config(slide, masks)
            html = build_post_html(config, server.xopat_url, width, height)
            _ipy_display(HTML(html))
        else:
            # simple case - no masks, use existing ?slides= URL
            url = build_simple_url(slide, server.xopat_url)
            _render_iframe(url, width, height)
    else:
        raise ValueError("slide_or_layer must be a string path or Layer object")


def _render_iframe(url, width, height):
    """Render URL as IFrame, with retry logic for JupyterHub."""
    prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "")

    if not prefix:
        _ipy_display(IFrame(url, width=width, height=height))
        return

    uid = hashlib.md5(url.encode()).hexdigest()[:8]
    _ipy_display(HTML(f"""
<div id="status-{uid}">Loading...</div>
<iframe
    id="frame-{uid}"
    src="{url}"
    width="{width}"
    height="{height}"
    style="border:1px solid #ccc; visibility: hidden;">
</iframe>
<script>
(function() {{
    const iframe = document.getElementById('frame-{uid}');
    const status = document.getElementById('status-{uid}');
    const maxRetries = 15;
    let attempt = 0;
    function isErrorPage() {{
        try {{
            const body = iframe.contentDocument?.body?.innerText || '';
            return body.includes('500') || body.includes('Internal server error');
        }} catch(e) {{
            return false;
        }}
    }}
    function retry() {{
        if (attempt >= maxRetries) {{
            status.textContent = 'Failed to load after 15 retries.';
            iframe.style.visibility = 'visible';
            return;
        }}
        attempt++;
        status.textContent = 'Retrying... attempt ' + attempt + '/15';
        setTimeout(() => {{
            iframe.contentWindow.location.reload();
        }}, 2000);
    }}
    iframe.onload = function() {{
        if (isErrorPage()) {{
            retry();
        }} else {{
            status.textContent = 'Ready.';
            iframe.style.visibility = 'visible';
        }}
    }};
}})();
</script>
"""))