#!/usr/bin/env python3

import hashlib
import json
import os
import urllib.parse
import urllib.request
from datetime import datetime
from io import BytesIO
from pathlib import Path

from PIL import Image, ImageOps

BASE_DIR = Path("/home/kot/birdnet-display")
IMAGE_DIR = BASE_DIR / "images"
OUTPUT_JSON = BASE_DIR / "daily.json"

DETECTIONS_URL = (
    "http://127.0.0.1:8080/api/v2/detections?limit=50"
)
SPECIES_IMAGE_URL = (
    "http://127.0.0.1:8080/api/v2/media/"
    "species-image?name={name}"
)
PUBLIC_BASE_URL = "https://kot.taildd909e.ts.net"

IMAGE_WIDTH = 240
IMAGE_HEIGHT = 180
MAX_ITEMS = 5

# GoodDisplay-Farbcodes:
# 0 Schwarz, 1 Weiß, 2 Gelb, 3 Rot, 5 Blau, 6 Grün
PALETTE_RGB = [
    (0, 0, 0),
    (255, 255, 255),
    (255, 220, 0),
    (220, 0, 0),
    (0, 90, 190),
    (0, 150, 60),
]
PALETTE_CODES = [0, 1, 2, 3, 5, 6]


def fetch_bytes(url):
    request = urllib.request.Request(
        url,
        headers={"User-Agent": "BirdNET-EInk-Pi/1.0"},
    )
    with urllib.request.urlopen(request, timeout=30) as response:
        return response.read()


def convert_image(scientific_name, target_path):
    encoded_name = urllib.parse.quote(scientific_name)
    url = SPECIES_IMAGE_URL.format(name=encoded_name)

    try:
        image_data = fetch_bytes(url)
        image = Image.open(BytesIO(image_data)).convert("RGB")
        print(f"Bild geladen: {scientific_name}")
    except Exception as exc:
        print(
            f"WARNUNG: Kein BirdNET-Bild fuer "
            f"{scientific_name}: {exc}"
        )

        # Ersatzkachel in Displayfarben
        image = Image.new(
            "RGB",
            (IMAGE_WIDTH, IMAGE_HEIGHT),
            (255, 255, 255),
        )
        image.paste(
            (0, 150, 60),
            (0, 0, IMAGE_WIDTH, 55),
        )
        image.paste(
            (255, 220, 0),
            (0, IMAGE_HEIGHT - 45,
             IMAGE_WIDTH, IMAGE_HEIGHT),
        )
        image.paste(
            (0, 90, 190),
            (80, 65, 160, 135),
        )

    image = ImageOps.fit(
        image,
        (IMAGE_WIDTH, IMAGE_HEIGHT),
        method=Image.Resampling.LANCZOS,
    )

    palette_image = Image.new("P", (1, 1))
    flat_palette = []

    for red, green, blue in PALETTE_RGB:
        flat_palette.extend((red, green, blue))

    flat_palette.extend([0] * (768 - len(flat_palette)))
    palette_image.putpalette(flat_palette)

    indexed = image.quantize(
        palette=palette_image,
        dither=Image.Dither.FLOYDSTEINBERG,
    )

    pixels = list(indexed.getdata())
    packed = bytearray(
        IMAGE_WIDTH * IMAGE_HEIGHT // 2
    )

    for position in range(0, len(pixels), 2):
        left = PALETTE_CODES[pixels[position]]
        right = PALETTE_CODES[pixels[position + 1]]
        packed[position // 2] = (
            (left << 4) | right
        )

    temporary_path = target_path.with_suffix(".tmp")
    temporary_path.write_bytes(packed)
    os.replace(temporary_path, target_path)


def main():
    IMAGE_DIR.mkdir(parents=True, exist_ok=True)

    response = json.loads(fetch_bytes(DETECTIONS_URL))
    detections = response.get("data", [])

    today = datetime.now().astimezone().strftime("%Y-%m-%d")
    detections = [
        detection
        for detection in detections
        if detection.get("date") == today
    ][:MAX_ITEMS]

    items = []

    for detection in detections:
        scientific_name = detection.get(
            "scientificName", ""
        ).strip()

        if not scientific_name:
            continue

        image_id = hashlib.sha1(
            scientific_name.encode("utf-8")
        ).hexdigest()[:12]

        image_name = f"{image_id}.epd"
        image_path = IMAGE_DIR / image_name
        expected_size = IMAGE_WIDTH * IMAGE_HEIGHT // 2

        if (
            not image_path.exists()
            or image_path.stat().st_size != expected_size
        ):
            convert_image(scientific_name, image_path)

        confidence = round(
            float(detection.get("confidence", 0)) * 100
        )

        items.append(
            {
                "id": int(detection.get("id", 0)),
                "time": str(detection.get("time", ""))[:5],
                "common": detection.get(
                    "commonName", scientific_name
                ),
                "scientific": scientific_name,
                "confidence": confidence,
                "image": (
                    f"{PUBLIC_BASE_URL}/images/{image_name}"
                ),
            }
        )

    output = {
        "generated": datetime.now()
        .astimezone()
        .isoformat(timespec="seconds"),
        "date": today,
        "count": len(items),
        "image_width": IMAGE_WIDTH,
        "image_height": IMAGE_HEIGHT,
        "items": items,
    }

    temporary_json = OUTPUT_JSON.with_suffix(".json.tmp")
    temporary_json.write_text(
        json.dumps(
            output,
            ensure_ascii=False,
            indent=2,
        )
        + "\n",
        encoding="utf-8",
    )
    os.replace(temporary_json, OUTPUT_JSON)


if __name__ == "__main__":
    main()
