import asyncio
import base64
import json
import os
import sys
from io import BytesIO

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
VENDOR = os.path.join(os.path.dirname(__file__), "vendor")
if os.path.isdir(VENDOR):
    sys.path.insert(0, VENDOR)

from PIL import Image, ImageDraw, ImageFont
from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager
from winrt.windows.storage.streams import Buffer, InputStreamOptions


def json_line(value):
    print(json.dumps(value, ensure_ascii=False, separators=(",", ":")))


def duration_ms(value):
    try:
        return int(max(0, round(value.total_seconds() * 1000)))
    except Exception:
        return 0


async def read_thumbnail_data_url(thumbnail):
    if thumbnail is None:
        return ""
    stream = await thumbnail.open_read_async()
    size = int(getattr(stream, "size", 0) or 0)
    if size <= 0 or size > 3_000_000:
        return ""
    buffer = Buffer(size)
    await stream.read_async(buffer, buffer.capacity, InputStreamOptions.NONE)
    data = bytes(buffer)
    mime = getattr(stream, "content_type", "") or "image/png"
    return {
        "dataUrl": "data:{};base64,{}".format(mime, base64.b64encode(data).decode("ascii")),
        "bytes": data
    }


def fit_text(draw, text, font_path, max_width, start_size, min_size):
    value = str(text or "").strip()
    if not value:
        value = "Bravent Music - G"
    for size in range(start_size, min_size - 1, -1):
        font = ImageFont.truetype(font_path, size)
        bbox = draw.textbbox((0, 0), value, font=font)
        if bbox[2] - bbox[0] <= max_width:
            return value, font
    font = ImageFont.truetype(font_path, min_size)
    while value and draw.textbbox((0, 0), value + "...", font=font)[2] > max_width:
        value = value[:-1]
    return (value + "...") if value else "...", font


def preferred_font_path(bold=False, text=""):
    windir = os.environ.get("WINDIR", "C:\\Windows")
    value = str(text or "")
    if any("\uac00" <= ch <= "\ud7af" for ch in value):
        names = ["malgunbd.ttf" if bold else "malgun.ttf", "msyhbd.ttc" if bold else "msyh.ttc"]
    elif any("\u3040" <= ch <= "\u30ff" for ch in value):
        names = ["YuGothB.ttc" if bold else "YuGothR.ttc", "meiryob.ttc" if bold else "meiryo.ttc"]
    elif any("\u3400" <= ch <= "\u9fff" or "\uf900" <= ch <= "\ufaff" for ch in value):
        names = ["msyhbd.ttc" if bold else "msyh.ttc", "msjhbd.ttc" if bold else "msjh.ttc"]
    else:
        names = []
    names += [
        "msyhbd.ttc" if bold else "msyh.ttc",
        "msjhbd.ttc" if bold else "msjh.ttc",
        "YuGothB.ttc" if bold else "YuGothR.ttc",
        "meiryob.ttc" if bold else "meiryo.ttc",
        "malgunbd.ttf" if bold else "malgun.ttf",
        "segoeuib.ttf" if bold else "segoeui.ttf",
        "arialbd.ttf" if bold else "arial.ttf",
    ]
    for name in names:
        path = os.path.join(windir, "Fonts", name)
        if os.path.exists(path):
            return path
    return os.path.join(windir, "Fonts", "arial.ttf")


def draw_text_line(target, text, font_path, x, y, width, height, size, min_size, fill, scroll=False, phase=0):
    value = str(text or "").strip() or "Bravent Music - G"
    draw = ImageDraw.Draw(target)
    font = ImageFont.truetype(font_path, size)
    bbox = draw.textbbox((0, 0), value, font=font)
    text_width = max(1, bbox[2] - bbox[0])
    text_height = max(1, bbox[3] - bbox[1])
    if scroll:
        phase = max(0.0, min(1.0, float(phase or 0)))
        if text_width <= width:
            offset_x = int((width - text_width) * phase)
        else:
            offset_x = int((width - text_width) * (1 - phase))
    else:
        offset_x = int((width - text_width) / 2)
    line = Image.new("RGBA", (width, height), (0, 0, 0, 0))
    line_draw = ImageDraw.Draw(line)
    line_draw.text((offset_x, (height - text_height) / 2 - bbox[1]), value, font=font, fill=fill)
    target.alpha_composite(line, (x, y))


def cover_key_data_url(thumbnail_bytes, title, artist):
    if not thumbnail_bytes:
        return ""
    try:
        source = Image.open(BytesIO(thumbnail_bytes)).convert("RGB")
        size = 144
        src_ratio = source.width / max(1, source.height)
        dst_ratio = 1
        if src_ratio > dst_ratio:
            new_h = size
            new_w = round(size * src_ratio)
        else:
            new_w = size
            new_h = round(size / src_ratio)
        source = source.resize((new_w, new_h), Image.LANCZOS)
        left = max(0, (new_w - size) // 2)
        top = max(0, (new_h - size) // 2)
        canvas = source.crop((left, top, left + size, top + size)).convert("RGBA")
        overlay = Image.new("RGBA", (size, size), (0, 0, 0, 0))
        draw = ImageDraw.Draw(overlay)
        draw.rectangle((0, 0, size, 34), fill=(0, 0, 0, 118))
        draw.rectangle((0, 108, size, 144), fill=(0, 0, 0, 148))
        artist_font_path = preferred_font_path(True, artist)
        title_font_path = preferred_font_path(True, title)
        artist_text, artist_font = fit_text(draw, artist, artist_font_path, 126, 13, 8)
        title_text, title_font = fit_text(draw, title, title_font_path, 126, 14, 8)
        artist_bbox = draw.textbbox((0, 0), artist_text, font=artist_font)
        title_bbox = draw.textbbox((0, 0), title_text, font=title_font)
        draw.text(((size - (artist_bbox[2] - artist_bbox[0])) / 2, 10), artist_text, font=artist_font, fill=(248, 242, 232, 255))
        draw.text(((size - (title_bbox[2] - title_bbox[0])) / 2, 121), title_text, font=title_font, fill=(253, 218, 176, 255))
        out = Image.alpha_composite(canvas, overlay).convert("RGB")
        buffer = BytesIO()
        out.save(buffer, format="PNG")
        return "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
    except Exception:
        return ""


def cover_render_data_url(payload):
    try:
        cover = data_url_to_image(payload.get("coverDataUrl", ""))
        if cover is None:
            return ""
        size = 144
        canvas = Image.new("RGBA", (size, size), (16, 24, 45, 255))
        src_ratio = cover.width / max(1, cover.height)
        if src_ratio > 1:
            new_h = size
            new_w = round(size * src_ratio)
        else:
            new_w = size
            new_h = round(size / src_ratio)
        fitted = cover.resize((new_w, new_h), Image.LANCZOS)
        left = max(0, (new_w - size) // 2)
        top = max(0, (new_h - size) // 2)
        canvas = fitted.crop((left, top, left + size, top + size)).convert("RGBA")

        overlay = Image.new("RGBA", (size, size), (0, 0, 0, 0))
        draw = ImageDraw.Draw(overlay)
        show_text = bool(payload.get("coverTextVisible", True))
        show_song = bool(payload.get("coverSongVisible", True))
        show_artist = bool(payload.get("coverArtistVisible", True))
        show_bars = bool(payload.get("coverBarsVisible", True))
        font_large = str(payload.get("coverFontSize", "normal")) == "large"
        text_mode = str(payload.get("coverTextMode", "still") or "still")
        scroll_phase = float(payload.get("scrollPhase", 0) or 0)
        title = str(payload.get("title", "") or "Live Cover")
        artist = str(payload.get("artist", "") or "Waiting for music")
        size_factor = 1
        if size >= 220:
            size_factor = 1.22
        elif size >= 180:
            size_factor = 1.12
        if show_text:
            title_size = int((14 if font_large else 13) * size_factor)
            artist_size = int((13 if font_large else 12) * size_factor)
            title_font_path = preferred_font_path(True, title)
            artist_font_path = preferred_font_path(True, artist)
            if show_song:
                if show_bars:
                    top_bar = max(30, title_size + 10)
                    draw.rectangle((0, 0, size, top_bar), fill=(0, 0, 0, 136))
                else:
                    top_bar = 28
                draw_text_line(overlay, title, title_font_path, 8, 2, 128, top_bar - 6 if show_bars else 28, title_size, 12, (248, 242, 232, 255), text_mode == "scroll", scroll_phase)
            if show_artist:
                if show_bars:
                    bottom_bar = max(30, artist_size + 10)
                    draw.rectangle((0, size - bottom_bar, size, size), fill=(0, 0, 0, 160))
                else:
                    bottom_bar = 28
                draw_text_line(overlay, artist, artist_font_path, 8, size - (bottom_bar - 7 if show_bars else 29), 128, bottom_bar - 6 if show_bars else 28, artist_size, 11, (253, 218, 176, 255), text_mode == "scroll", scroll_phase)
        out = Image.alpha_composite(canvas, overlay).convert("RGB")
        buffer = BytesIO()
        out.save(buffer, format="PNG")
        return "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
    except Exception:
        return ""


def hex_to_rgba(value, alpha):
    text = str(value or "#ff4757").strip().lstrip("#")
    if len(text) == 3:
        text = "".join(ch + ch for ch in text)
    try:
        return (int(text[0:2], 16), int(text[2:4], 16), int(text[4:6], 16), alpha)
    except Exception:
        return (255, 71, 87, alpha)


def data_url_to_image(data_url):
    if not data_url or "," not in data_url:
        return None
    payload = data_url.split(",", 1)[1]
    return Image.open(BytesIO(base64.b64decode(payload))).convert("RGBA")


def cov_wave_data_url(payload):
    try:
        cover = data_url_to_image(payload.get("coverDataUrl", ""))
        if cover is None:
            return ""
        level = max(0.0, min(1.0, float(payload.get("level", 0))))
        tick = int(payload.get("tick", 0))
        color = payload.get("color", "#ff4757")
        canvas = Image.new("RGBA", (144, 144), (0, 0, 0, 0))
        draw = ImageDraw.Draw(canvas)
        for index in range(3):
            phase = ((tick % 18) / 18 + index / 3) % 1
            size = 84 + phase * 66 + level * 26
            left = 72 - size / 2
            alpha = int((70 + level * 90) * (1 - phase))
            width = int(2 + level * 5)
            draw.rounded_rectangle((left, left, left + size, left + size), radius=int(18 + level * 10), outline=hex_to_rgba(color, max(0, alpha)), width=max(1, width))
        for index in range(16):
            phase = index / 16 * 6.28318 + tick * 0.06
            radius = 54 + level * 22
            size = 2 + level * 8
            x = 72 + __import__("math").cos(phase) * radius - size / 2
            y = 72 + __import__("math").sin(phase) * radius - size / 2
            draw.rounded_rectangle((x, y, x + size, y + size), radius=2, fill=hex_to_rgba(color, int(45 + level * 130)))
        cover_size = int(payload.get("coverSize", 96))
        cover_size = max(64, min(132, cover_size))
        cover_x = (144 - cover_size) // 2
        cover = cover.resize((cover_size, cover_size), Image.LANCZOS)
        mask = Image.new("L", (cover_size, cover_size), 0)
        ImageDraw.Draw(mask).rounded_rectangle((0, 0, cover_size, cover_size), radius=16, fill=255)
        canvas.alpha_composite(cover, (cover_x, cover_x), mask)
        buffer = BytesIO()
        canvas.save(buffer, format="PNG")
        return "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
    except Exception:
        return ""


async def state():
    manager = await MediaManager.request_async()
    session = manager.get_current_session() if manager else None
    if not session:
        return {
            "ok": True,
            "session": False,
            "title": "Bravent Music - G",
            "artist": "No active media session",
            "album": "",
            "playbackStatus": "NoSession",
            "positionMs": 0,
            "endMs": 180000,
            "thumbnailDataUrl": "",
            "helper": "python-winrt"
        }

    props = await session.try_get_media_properties_async()
    playback = session.get_playback_info()
    timeline = session.get_timeline_properties()
    thumbnail = await read_thumbnail_data_url(props.thumbnail)
    thumbnail_data_url = thumbnail["dataUrl"] if thumbnail else ""
    thumbnail_bytes = thumbnail["bytes"] if thumbnail else b""
    title = str(getattr(props, "title", "") or "")
    artist = str(getattr(props, "artist", "") or "")
    return {
        "ok": True,
        "session": True,
        "title": title,
        "artist": artist,
        "album": str(getattr(props, "album_title", "") or ""),
        "playbackStatus": getattr(playback.playback_status, "name", str(playback.playback_status)),
        "positionMs": duration_ms(timeline.position),
        "endMs": max(1, duration_ms(timeline.end_time)),
        "thumbnailDataUrl": thumbnail_data_url,
        "coverKeyDataUrl": cover_key_data_url(thumbnail_bytes, title, artist),
        "helper": "python-winrt"
    }


async def main():
    command = sys.argv[1] if len(sys.argv) > 1 else "state"
    if command == "cov":
        payload = json.loads((sys.stdin.read() or "{}").replace("\ufeff", ""))
        json_line({"ok": True, "covDataUrl": cov_wave_data_url(payload)})
        return 0
    if command == "cover":
        payload = json.loads((sys.stdin.read() or "{}").replace("\ufeff", ""))
        json_line({"ok": True, "coverDataUrl": cover_render_data_url(payload)})
        return 0
    if command != "state":
        json_line({"ok": False, "command": command, "error": "unsupported command"})
        return 2
    json_line(await state())
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(asyncio.run(main()))
    except Exception as error:
        json_line({"ok": False, "error": str(error), "helper": "python-winrt"})
        raise SystemExit(1)



