import os
import sys
import shutil

from geo.render.canvas import Canvas
from geo.render.braille import encode_braille
from geo.render.viewport import Viewport
from geo.render.rasterizer import rasterize_feature

MAX_FEATURES = 10000


def _read_key() -> str:
    """Read a single keypress, cross-platform."""
    if sys.platform == "win32":
        import msvcrt
        ch = msvcrt.getwch()
        if ch in ("\x00", "\xe0"):
            # Extended key: read the second byte
            ext = msvcrt.getwch()
            return {
                "H": "UP", "P": "DOWN",
                "K": "LEFT", "M": "RIGHT",
            }.get(ext, "")
        return ch
    else:
        import tty
        import termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            ch = sys.stdin.read(1)
            if ch == "\x1b":
                ch2 = sys.stdin.read(1)
                if ch2 == "[":
                    ch3 = sys.stdin.read(1)
                    return {
                        "A": "UP", "B": "DOWN",
                        "C": "RIGHT", "D": "LEFT",
                    }.get(ch3, "")
                return "ESC"
            return ch
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)


def _clear_screen():
    if sys.platform == "win32":
        os.system("cls")
    else:
        sys.stdout.write("\033[2J\033[H")
        sys.stdout.flush()


def render_static(path: str, bbox: tuple[float, float, float, float] | None = None) -> str:
    """Render a dataset to braille and return as a string (non-interactive)."""
    import fiona
    from shapely.geometry import shape

    term_width, term_height = shutil.get_terminal_size()
    char_width = term_width
    char_height = term_height - 3  # reserve for status

    with fiona.open(path) as src:
        bounds = bbox if bbox else src.bounds
        crs_epsg = src.crs.to_epsg() if src.crs else None
        crs_label = f"EPSG:{crs_epsg}" if crs_epsg else "unknown CRS"
        total = len(src)

        viewport = Viewport.from_bounds(
            bounds,
            pixel_width=char_width * 2,
            pixel_height=char_height * 4,
        )
        canvas = Canvas(char_width, char_height)

        count = 0
        sample_every = max(1, total // MAX_FEATURES) if total > MAX_FEATURES else 1

        if bbox:
            features = src.filter(bbox=bbox)
        else:
            features = src

        for i, feature in enumerate(features):
            if sample_every > 1 and i % sample_every != 0:
                continue
            rasterize_feature(feature, canvas, viewport)
            count += 1

    lines = encode_braille(canvas)
    sampled = f" (sampled: {count}/{total})" if sample_every > 1 else ""
    status = f"[{path}] {crs_label} | {char_width*2}x{char_height*4}px | {count} features{sampled}"
    lines.append(status)
    return "\n".join(lines)


def render_interactive(path: str, bbox: tuple[float, float, float, float] | None = None):
    """Interactive terminal map with zoom/pan controls."""
    import fiona

    term_width, term_height = shutil.get_terminal_size()
    char_width = term_width
    char_height = term_height - 3

    with fiona.open(path) as src:
        bounds = bbox if bbox else src.bounds
        total = len(src)
        crs_str = src.crs.to_epsg()
        crs_label = f"EPSG:{crs_str}" if crs_str else "unknown CRS"

    viewport = Viewport.from_bounds(
        bounds,
        pixel_width=char_width * 2,
        pixel_height=char_height * 4,
    )

    while True:
        canvas = Canvas(char_width, char_height)
        count = 0

        with fiona.open(path) as src:
            vp_bbox = (viewport.minx, viewport.miny, viewport.maxx, viewport.maxy)
            sample_every = max(1, total // MAX_FEATURES) if total > MAX_FEATURES else 1

            for i, feature in enumerate(src.filter(bbox=vp_bbox)):
                if sample_every > 1 and i % sample_every != 0:
                    continue
                rasterize_feature(feature, canvas, viewport)
                count += 1

        _clear_screen()
        lines = encode_braille(canvas)
        for line in lines:
            sys.stdout.write(line + "\n")

        sampled = f" (sampled: {count}/{total})" if sample_every > 1 else ""
        status = (f"[{path}] {crs_label} | {char_width*2}x{char_height*4}px | "
                  f"{count} features{sampled}")
        controls = "+/- zoom | arrows pan | r reset | q quit"
        sys.stdout.write(f"{status}\n{controls}")
        sys.stdout.flush()

        key = _read_key()
        if key in ("q", "Q", "\x03"):  # q or Ctrl+C
            _clear_screen()
            break
        elif key == "+":
            viewport = viewport.zoom(0.5)
        elif key == "-":
            viewport = viewport.zoom(2.0)
        elif key == "UP":
            viewport = viewport.pan(0, 0.25)
        elif key == "DOWN":
            viewport = viewport.pan(0, -0.25)
        elif key == "LEFT":
            viewport = viewport.pan(-0.25, 0)
        elif key == "RIGHT":
            viewport = viewport.pan(0.25, 0)
        elif key in ("r", "R"):
            viewport = Viewport.from_bounds(
                bounds,
                pixel_width=char_width * 2,
                pixel_height=char_height * 4,
            )
