import shutil
import os
from pathlib import Path
from urllib.parse import urljoin
from xml.sax.saxutils import escape

from PIL import Image, ImageDraw, ImageFont

from .context import AppContext
from .models import MovieMetadata


def build_output_dir(ctx: AppContext, actor_names: list[str], folder_name: str) -> Path:
    number_folder = sanitize_text(folder_name)
    cleaned_actors = [sanitize_text(x) for x in actor_names[:3] if sanitize_text(x)]
    if cleaned_actors:
        actor_folder = ",".join(cleaned_actors)
        out = Path(ctx.config.success_folder()) / actor_folder / number_folder
    else:
        out = Path(ctx.config.success_folder()) / number_folder
    out.mkdir(parents=True, exist_ok=True)
    return out


def download_cover(
    ctx: AppContext,
    cover_url: str,
    out_dir: Path,
    number: str,
    referer_url: str = "",
) -> tuple[Path, Path]:
    fanart = out_dir / f"{number}-fanart.jpg"
    poster = out_dir / f"{number}-poster.jpg"
    resolved_cover_url = _resolve_cover_url(cover_url)

    headers = {
        "Referer": referer_url,
        "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
        "User-Agent": (
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/128.0.0.0 Safari/537.36"
        ),
    }
    cookies = ctx.config.javbus_cookies()
    content = ctx.http.get_bytes(resolved_cover_url, headers=headers, cookies=cookies)
    fanart.write_bytes(content)

    with Image.open(fanart) as img:
        w, h = img.size
        crop = img.crop((w / 1.9, 0, w, h))
        crop.save(poster)

    thumb = out_dir / f"{number}-thumb.jpg"
    shutil.copyfile(fanart, thumb)
    return fanart, poster


def apply_subtitle_badge(ctx: AppContext, poster_path: Path, backup_enabled: bool = True) -> None:
    backup_path = poster_path.with_name(poster_path.stem + "_ori" + poster_path.suffix)
    source_path = poster_path
    if backup_enabled:
        if not backup_path.exists():
            shutil.copy2(poster_path, backup_path)
        source_path = backup_path
    _paste_badge(
        ctx=ctx,
        img_path=source_path,
        out_path=poster_path,
        text="字幕",
        position="bottom-right",
        scale=0.3,
        margin=30,
        font_size=64,
        keep_alpha=False,
    )


def _resolve_cover_url(cover_url: str) -> str:
    url = cover_url.strip()
    if not url:
        return url
    if url.startswith("//"):
        return f"https:{url}"
    if url.startswith("/"):
        return urljoin("https://www.javbus.com", url)
    return url


def write_nfo(ctx: AppContext, meta: MovieMetadata, out_dir: Path, has_subtitle: bool = False) -> Path:
    output_number = f"{meta.number}-C" if has_subtitle else meta.number
    title = sanitize_text(f"{output_number}-{meta.title}")
    nfo = out_dir / f"{output_number}.nfo"
    xml_escape_map = {'"': "&quot;", "'": "&apos;"}

    with nfo.open("w", encoding="utf-8") as f:
        f.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
        f.write("<movie>\n")
        f.write(f"  <title>{escape(title, xml_escape_map)}</title>\n")
        f.write(f"  <studio>{escape(meta.studio, xml_escape_map)}</studio>\n")
        f.write(f"  <year>{escape(meta.year, xml_escape_map)}</year>\n")
        f.write(f"  <plot>{escape(meta.outline, xml_escape_map)}</plot>\n")
        f.write(f"  <runtime>{escape(meta.runtime, xml_escape_map)}</runtime>\n")
        f.write(f"  <director>{escape(meta.director, xml_escape_map)}</director>\n")
        f.write(f"  <poster>{output_number}-poster.jpg</poster>\n")
        f.write(f"  <thumb>{output_number}-thumb.jpg</thumb>\n")
        f.write(f"  <fanart>{output_number}-fanart.jpg</fanart>\n")
        for actor in meta.actor:
            f.write("  <actor>\n")
            f.write(f"    <name>{escape(actor, xml_escape_map)}</name>\n")
            f.write("  </actor>\n")
        for t in meta.tag:
            escaped_tag = escape(t, xml_escape_map)
            f.write(f"  <tag>{escaped_tag}</tag>\n")
            f.write(f"  <genre>{escaped_tag}</genre>\n")
        if has_subtitle:
            f.write("  <tag>中文字幕</tag>\n")
            f.write("  <genre>中文字幕</genre>\n")
        f.write(f"  <num>{escape(output_number, xml_escape_map)}</num>\n")
        f.write(f"  <premiered>{escape(meta.release, xml_escape_map)}</premiered>\n")
        f.write(f"  <cover>{escape(meta.cover, xml_escape_map)}</cover>\n")
        f.write(f"  <website>{escape(meta.website, xml_escape_map)}</website>\n")
        f.write("</movie>\n")

    return nfo


def move_video(video: Path, out_dir: Path, number: str) -> Path:
    dest = out_dir / f"{number}{video.suffix}"
    shutil.move(str(video), str(dest))
    return dest


def move_failed_video(ctx: AppContext, video: Path) -> Path:
    failed_dir = Path(ctx.config.failed_folder())
    failed_dir.mkdir(parents=True, exist_ok=True)

    dest = failed_dir / video.name
    if dest.exists():
        stem = video.stem
        suffix = video.suffix
        index = 1
        while True:
            candidate = failed_dir / f"{stem}_{index}{suffix}"
            if not candidate.exists():
                dest = candidate
                break
            index += 1

    shutil.move(str(video), str(dest))
    return dest


def link_video_reference(video: Path, out_dir: Path, number: str) -> Path:
    dest = out_dir / f"{number}{video.suffix}"
    if dest.exists() or dest.is_symlink():
        dest.unlink()
    dest.symlink_to(video.resolve())
    return dest


def sanitize_text(value: str) -> str:
    out = value
    for ch in "\\/:*?\"<>|":
        out = out.replace(ch, "")
    return out.strip()


CJK_FONTS = [
    "/System/Library/Fonts/PingFang.ttc",
    "/System/Library/Fonts/Hiragino Sans GB W3.ttc",
    "/System/Library/Fonts/Hiragino Sans GB.ttc",
    "/Library/Fonts/Arial Unicode.ttf",
    "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
    "/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc",
    "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
    "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
]


def _load_cjk_font(size: int):
    for p in CJK_FONTS:
        if os.path.exists(p):
            try:
                return ImageFont.truetype(p, size=size)
            except Exception:
                pass
    return ImageFont.load_default()


def _build_badge(
    text: str = "字幕",
    font_size: int = 64,
    padding_x: int = 36,
    padding_y: int = 18,
    radius: int = 28,
    bg=(44, 185, 90),
    fg=(255, 255, 255),
    shadow: bool = False,
    outline=None,
):
    font = _load_cjk_font(font_size)
    dummy = Image.new("RGBA", (1, 1))
    draw = ImageDraw.Draw(dummy)
    l, t, r, b = draw.textbbox((0, 0), text, font=font)
    text_w, text_h = r - l, b - t

    w = text_w + padding_x * 2
    h = text_h + padding_y * 2
    badge = Image.new("RGBA", (w + 8, h + 8), (0, 0, 0, 0))
    d2 = ImageDraw.Draw(badge)

    if shadow:
        d2.rounded_rectangle((4, 4, 4 + w, 4 + h), radius=radius, fill=(0, 0, 0, 80))

    d2.rounded_rectangle((0, 0, w, h), radius=radius, fill=bg, outline=outline, width=2 if outline else 0)
    stroke_w = max(1, font_size // 18)
    d2.text(
        (padding_x, padding_y - t),
        text,
        font=font,
        fill=fg,
        stroke_width=stroke_w if outline is None else 0,
        stroke_fill=(0, 0, 0),
    )
    return badge


def _paste_badge(
    ctx: AppContext,
    img_path: Path,
    out_path: Path,
    text: str = "字幕",
    position: str = "bottom-right",
    scale: float = 0.3,
    margin: int = 30,
    font_size: int = 64,
    keep_alpha: bool = False,
) -> None:
    base = Image.open(img_path).convert("RGBA")
    badge = _build_badge(text=text, font_size=font_size)
    target_w = max(1, int(base.width * scale))
    ratio = target_w / badge.width
    badge = badge.resize((target_w, max(1, int(badge.height * ratio))), Image.LANCZOS)

    pos = position.lower()
    if pos == "top-left":
        x, y = margin, margin
    elif pos == "top-right":
        x, y = base.width - badge.width - margin, margin
    elif pos == "bottom-left":
        x, y = margin, base.height - badge.height - margin
    elif pos == "bottom-right":
        x, y = base.width - badge.width - margin, base.height - badge.height - int(margin * 0.75)
    elif pos == "center":
        x, y = (base.width - badge.width) // 2, (base.height - badge.height) // 2
    else:
        raise ValueError("position must be top-left/top-right/bottom-left/bottom-right/center")

    base.alpha_composite(badge, (x, y))
    out_path.parent.mkdir(parents=True, exist_ok=True)
    image_opts = ctx.config.image_save_options()
    if not keep_alpha:
        base.convert("RGB").save(
            out_path,
            quality=image_opts["jpeg_quality"],
            optimize=image_opts["optimize"],
            progressive=image_opts["progressive"],
        )
    else:
        if out_path.suffix.lower() in [".jpg", ".jpeg"]:
            base.convert("RGB").save(
                out_path,
                quality=image_opts["jpeg_quality"],
                optimize=image_opts["optimize"],
                progressive=image_opts["progressive"],
            )
        else:
            base.save(out_path)
