"""Asset Generator for LandingForge."""
import io
import logging
import os
import struct
import zlib

logger = logging.getLogger(__name__)


def _hex_to_rgb(hex_color: str):
    """Convert a hex color string to an (R, G, B) tuple."""
    h = hex_color.lstrip("#")
    if len(h) == 3:
        h = "".join(c * 2 for c in h)
    return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))


def _blend(c1, c2, t: float):
    """Linearly interpolate between two RGB tuples."""
    return tuple(int(c1[i] + (c2[i] - c1[i]) * t) for i in range(3))


def _make_png(width: int, height: int, pixels) -> bytes:
    """
    Build a minimal valid PNG from a flat list of (R,G,B) tuples.
    'pixels' is a list of height rows, each a list of (R,G,B) tuples.
    """
    def pack_chunk(chunk_type: bytes, data: bytes) -> bytes:
        length = struct.pack(">I", len(data))
        crc_val = zlib.crc32(chunk_type + data) & 0xFFFFFFFF
        crc_bytes = struct.pack(">I", crc_val)
        return length + chunk_type + data + crc_bytes

    # IHDR
    ihdr_data = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
    ihdr_chunk = pack_chunk(b"IHDR", ihdr_data)

    # IDAT: build raw scanlines
    raw = bytearray()
    for row in pixels:
        raw.append(0)  # filter byte
        for r, g, b in row:
            raw.extend([r & 0xFF, g & 0xFF, b & 0xFF])

    compressed = zlib.compress(bytes(raw), 9)
    idat_chunk = pack_chunk(b"IDAT", compressed)

    # IEND
    iend_chunk = pack_chunk(b"IEND", b"")

    signature = b"\x89PNG\r\n\x1a\n"
    return signature + ihdr_chunk + idat_chunk + iend_chunk


def _make_ico(png_16: bytes, png_32: bytes) -> bytes:
    """Build a minimal ICO file containing 16x16 and 32x32 PNG images."""
    num_images = 2
    # ICO header: reserved(2) + type(2) + count(2)
    ico_header = struct.pack("<HHH", 0, 1, num_images)
    # Each directory entry: width(1) + height(1) + colors(1) + reserved(1) +
    #                        planes(2) + bit_count(2) + size(4) + offset(4)
    dir_size = 16
    header_size = 6 + dir_size * num_images
    images = [png_16, png_32]
    sizes = [16, 32]
    dirs = b""
    offset = header_size
    for i, img in enumerate(images):
        sz = sizes[i] & 0xFF
        dirs += struct.pack("<BBBBHHII", sz, sz, 0, 0, 1, 32, len(img), offset)
        offset += len(img)
    return ico_header + dirs + b"".join(images)


def _gradient_pixels(width: int, height: int, color1, color2):
    """Generate a list of rows with a left-to-right gradient."""
    rows = []
    for y in range(height):
        row = []
        for x in range(width):
            t = x / max(width - 1, 1)
            row.append(_blend(color1, color2, t))
        rows.append(row)
    return rows


def _draw_letter_on_pixels(pixels, letter: str, color=(255, 255, 255)):
    """
    Draw a very simple 5x7 pixel font letter onto the center of a pixels array.
    Uses a minimal bitmap font for uppercase A-Z and digits 0-9.
    """
    FONT = {
        "A": ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
        "B": ["11110", "10001", "10001", "11110", "10001", "10001", "11110"],
        "C": ["01111", "10000", "10000", "10000", "10000", "10000", "01111"],
        "D": ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
        "E": ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
        "F": ["11111", "10000", "10000", "11110", "10000", "10000", "10000"],
        "G": ["01111", "10000", "10000", "10111", "10001", "10001", "01111"],
        "H": ["10001", "10001", "10001", "11111", "10001", "10001", "10001"],
        "I": ["11111", "00100", "00100", "00100", "00100", "00100", "11111"],
        "J": ["00111", "00010", "00010", "00010", "10010", "10010", "01100"],
        "K": ["10001", "10010", "10100", "11000", "10100", "10010", "10001"],
        "L": ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
        "M": ["10001", "11011", "10101", "10001", "10001", "10001", "10001"],
        "N": ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
        "O": ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
        "P": ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
        "Q": ["01110", "10001", "10001", "10001", "10101", "10010", "01101"],
        "R": ["11110", "10001", "10001", "11110", "10100", "10010", "10001"],
        "S": ["01111", "10000", "10000", "01110", "00001", "00001", "11110"],
        "T": ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
        "U": ["10001", "10001", "10001", "10001", "10001", "10001", "01110"],
        "V": ["10001", "10001", "10001", "10001", "10001", "01010", "00100"],
        "W": ["10001", "10001", "10001", "10101", "10101", "11011", "10001"],
        "X": ["10001", "01010", "00100", "00100", "00100", "01010", "10001"],
        "Y": ["10001", "10001", "01010", "00100", "00100", "00100", "00100"],
        "Z": ["11111", "00001", "00010", "00100", "01000", "10000", "11111"],
        "0": ["01110", "10001", "10011", "10101", "11001", "10001", "01110"],
        "1": ["00100", "01100", "00100", "00100", "00100", "00100", "11111"],
        "2": ["01110", "10001", "00001", "00110", "01000", "10000", "11111"],
        "3": ["11110", "00001", "00001", "01110", "00001", "00001", "11110"],
        "4": ["00010", "00110", "01010", "10010", "11111", "00010", "00010"],
        "5": ["11111", "10000", "10000", "11110", "00001", "00001", "11110"],
        "6": ["01110", "10000", "10000", "11110", "10001", "10001", "01110"],
        "7": ["11111", "00001", "00010", "00100", "01000", "01000", "01000"],
        "8": ["01110", "10001", "10001", "01110", "10001", "10001", "01110"],
        "9": ["01110", "10001", "10001", "01111", "00001", "00001", "01110"],
    }
    char = letter.upper()
    bitmap = FONT.get(char, FONT.get("A"))
    height = len(pixels)
    width = len(pixels[0]) if height > 0 else 0
    scale = max(1, min(width, height) // 10)
    bmp_h = len(bitmap)
    bmp_w = len(bitmap[0]) if bmp_h > 0 else 5
    draw_h = bmp_h * scale
    draw_w = bmp_w * scale
    start_y = (height - draw_h) // 2
    start_x = (width - draw_w) // 2
    for row_idx, row_bits in enumerate(bitmap):
        for col_idx, bit in enumerate(row_bits):
            if bit == "1":
                for sy in range(scale):
                    for sx in range(scale):
                        py = start_y + row_idx * scale + sy
                        px = start_x + col_idx * scale + sx
                        if 0 <= py < height and 0 <= px < width:
                            pixels[py][px] = color


class AssetGenerator:
    """Generates image assets, favicons, placeholder SVGs, and bundled fonts/icons."""

    def __init__(self, config: dict):
        self.config = config
        self.site = config["site"]
        self.design = config["design"]
        self.colors = config["design"]["colors"]
        self._pil_available = self._check_pil()

    def _check_pil(self) -> bool:
        try:
            import PIL  # noqa: F401
            return True
        except ImportError:
            return False

    def generate_og_image(self) -> bytes:
        """Generate a 1200x630 OG image."""
        brand = self.site["brand_name"]
        tagline = self.site.get("tagline", "")
        c1 = _hex_to_rgb(self.colors.get("accent_gradient_start", "#6c5ce7"))
        c2 = _hex_to_rgb(self.colors.get("accent_gradient_end", "#a29bfe"))

        if self._pil_available:
            return self._generate_og_image_pil(brand, tagline, c1, c2)
        return self._generate_og_image_fallback(brand, c1, c2)

    def _generate_og_image_pil(self, brand: str, tagline: str, c1, c2) -> bytes:
        from PIL import Image, ImageDraw, ImageFont  # noqa: PLC0415
        width, height = 1200, 630
        img = Image.new("RGB", (width, height))
        draw = ImageDraw.Draw(img)
        # Gradient background
        for x in range(width):
            t = x / (width - 1)
            color = _blend(c1, c2, t)
            draw.line([(x, 0), (x, height)], fill=color)
        # Dark overlay
        overlay = Image.new("RGBA", (width, height), (10, 10, 26, 160))
        img.paste(Image.new("RGB", (width, height), (10, 10, 26)), mask=overlay.split()[3])
        # Brand name
        try:
            font_large = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 80)
            font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 36)
        except (OSError, IOError):
            font_large = ImageFont.load_default()
            font_small = font_large
        draw = ImageDraw.Draw(img)
        # Brand
        bbox = draw.textbbox((0, 0), brand, font=font_large)
        text_w = bbox[2] - bbox[0]
        text_h = bbox[3] - bbox[1]
        draw.text(((width - text_w) // 2, height // 2 - text_h - 20), brand, font=font_large, fill=(255, 255, 255))
        # Tagline
        bbox2 = draw.textbbox((0, 0), tagline, font=font_small)
        tw2 = bbox2[2] - bbox2[0]
        draw.text(((width - tw2) // 2, height // 2 + 20), tagline, font=font_small, fill=(200, 200, 220))
        buf = io.BytesIO()
        img.save(buf, format="PNG", optimize=True)
        return buf.getvalue()

    def _generate_og_image_fallback(self, brand: str, c1, c2) -> bytes:
        width, height = 120, 63
        pixels = _gradient_pixels(width, height, c1, c2)
        first = brand[0] if brand else "L"
        _draw_letter_on_pixels(pixels, first)
        return _make_png(width, height, pixels)

    def generate_favicon(self) -> dict:
        """
        Return a dict of filename -> bytes for favicon files.
        Keys: favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png
        """
        brand = self.site["brand_name"]
        first = brand[0] if brand else "L"
        c1 = _hex_to_rgb(self.colors.get("accent_gradient_start", "#6c5ce7"))
        c2 = _hex_to_rgb(self.colors.get("accent_gradient_end", "#a29bfe"))

        png16 = self._make_icon_png(16, first, c1, c2)
        png32 = self._make_icon_png(32, first, c1, c2)
        png180 = self._make_icon_png(180, first, c1, c2)

        return {
            "favicon.ico": _make_ico(png16, png32),
            "favicon-16x16.png": png16,
            "favicon-32x32.png": png32,
            "apple-touch-icon.png": png180,
        }

    def _make_icon_png(self, size: int, letter: str, c1, c2) -> bytes:
        if self._pil_available:
            return self._make_icon_png_pil(size, letter, c1, c2)
        pixels = _gradient_pixels(size, size, c1, c2)
        _draw_letter_on_pixels(pixels, letter)
        return _make_png(size, size, pixels)

    def _make_icon_png_pil(self, size: int, letter: str, c1, c2) -> bytes:
        from PIL import Image, ImageDraw, ImageFont  # noqa: PLC0415
        img = Image.new("RGB", (size, size))
        draw = ImageDraw.Draw(img)
        for x in range(size):
            t = x / max(size - 1, 1)
            color = _blend(c1, c2, t)
            draw.line([(x, 0), (x, size)], fill=color)
        font_size = max(10, size // 2)
        try:
            font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size)
        except (OSError, IOError):
            font = ImageFont.load_default()
        bbox = draw.textbbox((0, 0), letter.upper(), font=font)
        tw = bbox[2] - bbox[0]
        th = bbox[3] - bbox[1]
        draw.text(((size - tw) // 2, (size - th) // 2), letter.upper(), font=font, fill=(255, 255, 255))
        buf = io.BytesIO()
        img.save(buf, format="PNG")
        return buf.getvalue()

    def generate_placeholder_svgs(self) -> dict:
        """
        Return a dict of filename -> SVG string for feature and step placeholders.
        """
        content = self.config.get("content", {})
        features = content.get("features", {}).get("tabs", [])
        steps = content.get("how_to", {}).get("steps", [])
        c1 = self.colors.get("background_secondary", "#12122a")
        c2 = self.colors.get("background_tertiary", "#1a1a3e")
        accent = self.colors.get("accent_primary", "#6c5ce7")

        icons = ["fa-robot", "fa-chart-line", "fa-chart-pie", "fa-users"]
        result = {}
        for i, tab in enumerate(features):
            icon = tab.get("icon", icons[i % len(icons)])
            alt = tab.get("image_alt", f"Feature {i + 1}")
            filename = f"placeholder-feature-{i}.svg"
            result[filename] = self._svg_placeholder(
                alt, icon, c1, c2, accent, aspect="16/9"
            )

        step_icons = ["fa-pencil", "fa-sliders", "fa-rocket"]
        for step in steps:
            num = step.get("number", 1)
            alt = step.get("image_alt", f"Step {num}")
            icon = step_icons[(num - 1) % len(step_icons)]
            filename = f"placeholder-step-{num}.svg"
            result[filename] = self._svg_placeholder(
                alt, icon, c1, c2, accent, aspect="16/9"
            )

        return result

    def _svg_placeholder(self, label: str, icon: str, bg1: str, bg2: str, accent: str, aspect: str = "16/9") -> str:
        # Use viewBox 1600x900 for 16:9
        w, h = 1600, 900
        return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" preserveAspectRatio="xMidYMid slice">
  <defs>
    <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
      <stop offset="0%" stop-color="{bg1}"/>
      <stop offset="100%" stop-color="{bg2}"/>
    </linearGradient>
  </defs>
  <rect width="{w}" height="{h}" fill="url(#g)"/>
  <circle cx="{w//2}" cy="{h//2 - 80}" r="120" fill="{accent}" opacity="0.18"/>
  <text x="{w//2}" y="{h//2 - 40}" text-anchor="middle" font-family="system-ui,sans-serif"
        font-size="96" fill="{accent}" opacity="0.6">&#xf544;</text>
  <text x="{w//2}" y="{h//2 + 80}" text-anchor="middle" font-family="system-ui,sans-serif"
        font-size="48" fill="#a0a0c0" opacity="0.7">{label}</text>
</svg>
"""

    def download_google_fonts(self, output_dir: str) -> dict:
        """
        Attempt to download Inter and JetBrains Mono fonts.
        Returns a dict of relative path -> bytes for font files + CSS content strings.
        On failure, returns minimal fallback CSS using system fonts.
        """
        import urllib.request  # noqa: PLC0415

        fonts_dir = os.path.join(output_dir, "assets", "fonts")
        inter_dir = os.path.join(fonts_dir, "inter-v13-latin")
        jb_dir = os.path.join(fonts_dir, "jetbrains-mono")
        os.makedirs(inter_dir, exist_ok=True)
        os.makedirs(jb_dir, exist_ok=True)

        files = {}

        # Try to download Inter woff2 files from Google Fonts static CDN
        font_urls = [
            (
                "https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2",
                os.path.join(inter_dir, "inter-v13-latin-regular.woff2"),
                "inter-v13-latin-regular.woff2",
            ),
            (
                "https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fAZ9hiA.woff2",
                os.path.join(inter_dir, "inter-v13-latin-700.woff2"),
                "inter-v13-latin-700.woff2",
            ),
            (
                "https://fonts.gstatic.com/s/jetbrainsmono/v18/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTOlOTk6OThhvA.woff2",
                os.path.join(jb_dir, "jetbrains-mono-regular.woff2"),
                "jetbrains-mono-regular.woff2",
            ),
        ]

        downloaded = {}
        for url, dest_path, fname in font_urls:
            try:
                req = urllib.request.Request(
                    url, headers={"User-Agent": "Mozilla/5.0 LandingForge/1.0"}
                )
                with urllib.request.urlopen(req, timeout=10) as resp:
                    data = resp.read()
                with open(dest_path, "wb") as f:
                    f.write(data)
                downloaded[fname] = data
                logger.info("Downloaded font: %s", fname)
            except Exception as exc:
                logger.warning("Could not download %s: %s", fname, exc)

        # Generate inter CSS
        if "inter-v13-latin-regular.woff2" in downloaded or "inter-v13-latin-700.woff2" in downloaded:
            inter_css = self._inter_css()
        else:
            inter_css = self._inter_css_fallback()

        inter_css_path = os.path.join(inter_dir, "inter-v13-latin.css")
        with open(inter_css_path, "w", encoding="utf-8") as f:
            f.write(inter_css)

        # Generate JetBrains Mono CSS
        if "jetbrains-mono-regular.woff2" in downloaded:
            jb_css = self._jetbrains_css()
        else:
            jb_css = self._jetbrains_css_fallback()

        jb_css_path = os.path.join(jb_dir, "jetbrains-mono.css")
        with open(jb_css_path, "w", encoding="utf-8") as f:
            f.write(jb_css)

        return {
            "inter_css": inter_css,
            "jetbrains_css": jb_css,
        }

    def _inter_css(self) -> str:
        return """/* Inter font — self-hosted */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('inter-v13-latin-regular.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('inter-v13-latin-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
"""

    def _inter_css_fallback(self) -> str:
        return """/* Inter font — system fallback (download failed) */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Inter Regular'), local('Inter-Regular'),
       local('system-ui'), local('-apple-system'), local('BlinkMacSystemFont');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: local('Inter Bold'), local('Inter-Bold'),
       local('system-ui'), local('-apple-system'), local('BlinkMacSystemFont');
}
"""

    def _jetbrains_css(self) -> str:
        return """/* JetBrains Mono — self-hosted */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('jetbrains-mono-regular.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
"""

    def _jetbrains_css_fallback(self) -> str:
        return """/* JetBrains Mono — system fallback (download failed) */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('JetBrains Mono'), local('Courier New'), local('monospace');
}
"""

    def bundle_fontawesome(self, output_dir: str) -> str:
        """
        Attempt to download Font Awesome 6 Free and extract CSS + woff2 files.
        Returns the fontawesome.min.css content (either real or minimal fallback).
        """
        import urllib.request  # noqa: PLC0415
        import zipfile  # noqa: PLC0415

        webfonts_dir = os.path.join(output_dir, "assets", "webfonts")
        css_dir = os.path.join(output_dir, "assets", "css")
        os.makedirs(webfonts_dir, exist_ok=True)
        os.makedirs(css_dir, exist_ok=True)

        fa_url = "https://use.fontawesome.com/releases/v6.5.1/fontawesome-free-6.5.1-web.zip"
        zip_path = os.path.join(output_dir, "_fa_tmp.zip")
        try:
            req = urllib.request.Request(
                fa_url, headers={"User-Agent": "Mozilla/5.0 LandingForge/1.0"}
            )
            logger.info("Downloading Font Awesome 6 Free...")
            with urllib.request.urlopen(req, timeout=30) as resp:
                data = resp.read()
            with open(zip_path, "wb") as f:
                f.write(data)

            with zipfile.ZipFile(zip_path, "r") as zf:
                names = zf.namelist()
                # Extract CSS
                css_candidates = [n for n in names if n.endswith("all.min.css") or n.endswith("fontawesome.min.css")]
                fa_css_content = ""
                if css_candidates:
                    with zf.open(css_candidates[0]) as cf:
                        raw_css = cf.read().decode("utf-8", errors="replace")
                    # Patch font paths to point to /assets/webfonts/
                    fa_css_content = raw_css.replace("../webfonts/", "/assets/webfonts/")
                # Extract woff2 files
                woff2_files = [n for n in names if n.endswith(".woff2")]
                for wf in woff2_files:
                    basename = os.path.basename(wf)
                    dest = os.path.join(webfonts_dir, basename)
                    with zf.open(wf) as src, open(dest, "wb") as dst:
                        dst.write(src.read())
                    logger.info("Extracted font: %s", basename)

            os.remove(zip_path)
            if fa_css_content:
                return fa_css_content
        except Exception as exc:
            logger.warning("Could not download Font Awesome: %s — using inline SVG fallback.", exc)
            if os.path.exists(zip_path):
                os.remove(zip_path)

        # Fallback: minimal CSS with common icons as inline SVG data URIs
        return self._fontawesome_fallback_css()

    def _fontawesome_fallback_css(self) -> str:
        """Minimal Font Awesome fallback using CSS content with Unicode codepoints."""
        return r"""/* Font Awesome 6 Free — minimal fallback
   (full version could not be downloaded) */

.fa, .fas, .far, .fab, .fa-solid, .fa-regular, .fa-brands {
  font-style: normal;
  font-variant: normal;
  text-rendering: auto;
  line-height: 1;
  display: inline-block;
}

/* Common icon Unicode fallbacks via content */
.fa-robot::before       { content: "\f544"; }
.fa-chart-line::before  { content: "\f201"; }
.fa-chart-pie::before   { content: "\f200"; }
.fa-users::before       { content: "\f0c0"; }
.fa-check::before       { content: "\f00c"; }
.fa-arrow-up::before    { content: "\f062"; }
.fa-bars::before        { content: "\f0c9"; }
.fa-times::before       { content: "\f00d"; }
.fa-globe::before       { content: "\f0ac"; }
.fa-chevron-down::before{ content: "\f078"; }
.fa-chevron-up::before  { content: "\f077"; }
.fa-play::before        { content: "\f04b"; }
.fa-star::before        { content: "\f005"; }
.fa-pencil::before      { content: "\f303"; }
.fa-sliders::before     { content: "\f1de"; }
.fa-rocket::before      { content: "\f135"; }
.fa-brands.fa-x-twitter::before  { content: "\e61b"; }
.fa-brands.fa-github::before     { content: "\f09b"; }
.fa-brands.fa-linkedin::before   { content: "\f08c"; }
"""
