from __future__ import annotations

import json
import mimetypes
import subprocess
import uuid
from typing import Any
from urllib import error, parse, request

from app.config import settings


class KitchenOwlService:
    def __init__(self) -> None:
        self.base_url = settings.kitchenowl_base_url.rstrip("/")
        self.token = settings.kitchenowl_api_token
        self.household_id = settings.kitchenowl_household_id

    def configured(self) -> bool:
        return bool(self.base_url and self.token and self.household_id)

    def _request(self, method: str, path: str, *, params: dict[str, Any] | None = None, body: dict[str, Any] | None = None, timeout: int = 30):
        url = f"{self.base_url}{path}"
        if params:
            url += ("&" if "?" in url else "?") + parse.urlencode(params, doseq=True)
        data = None
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Accept": "application/json",
            "User-Agent": "doris-kitchen/1.0",
        }
        if body is not None:
            data = json.dumps(body).encode("utf-8")
            headers["Content-Type"] = "application/json"
        req = request.Request(url, data=data, headers=headers, method=method.upper())
        try:
            with request.urlopen(req, timeout=timeout) as resp:
                raw = resp.read().decode("utf-8", "replace")
                content_type = resp.headers.get("Content-Type", "")
                parsed = json.loads(raw) if "json" in content_type and raw else raw
                return resp.status, parsed
        except error.HTTPError as exc:
            raw = exc.read().decode("utf-8", "replace")
            try:
                parsed = json.loads(raw)
            except Exception:
                parsed = raw
            return exc.code, parsed

    def _fetch_binary(self, url: str, timeout: int = 45) -> tuple[bytes, str]:
        req = request.Request(
            url,
            headers={
                "User-Agent": "Mozilla/5.0 (Doris Kitchen)",
                "Accept": "image/*,*/*;q=0.8",
                "Accept-Language": "en-US,en;q=0.9",
            },
        )
        try:
            with request.urlopen(req, timeout=timeout) as resp:
                mime_type = resp.headers.get_content_type() or "application/octet-stream"
                return resp.read(), mime_type
        except error.HTTPError as exc:
            if exc.code not in {403, 406, 429}:
                raise
        curl = subprocess.run(
            [
                "curl", "-fsSL", "--max-time", str(timeout),
                "-A", "Mozilla/5.0 (Doris Kitchen)",
                "-H", "Accept: image/*,*/*;q=0.8",
                "-H", "Accept-Language: en-US,en;q=0.9",
                url,
            ],
            capture_output=True,
            check=False,
        )
        if curl.returncode != 0:
            raise RuntimeError(f"Failed to fetch image URL: {url}")
        guessed = mimetypes.guess_type(url)[0] or "application/octet-stream"
        return curl.stdout, guessed

    def _upload_photo_from_url(self, photo_url: str) -> str | None:
        photo_url = str(photo_url or "").strip()
        if not photo_url.startswith(("http://", "https://")):
            return photo_url or None
        content, mime_type = self._fetch_binary(photo_url)
        ext = mimetypes.guess_extension(mime_type) or ".jpg"
        filename = f"doris-{uuid.uuid4().hex}{ext}"
        boundary = f"----DorisBoundary{uuid.uuid4().hex}"
        parts = [
            f"--{boundary}\r\n".encode("utf-8"),
            f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode("utf-8"),
            f"Content-Type: {mime_type}\r\n\r\n".encode("utf-8"),
            content,
            f"\r\n--{boundary}--\r\n".encode("utf-8"),
        ]
        data = b"".join(parts)
        req = request.Request(
            f"{self.base_url}/api/upload",
            data=data,
            headers={
                "Authorization": f"Bearer {self.token}",
                "Accept": "application/json",
                "Content-Type": f"multipart/form-data; boundary={boundary}",
                "User-Agent": "doris-kitchen/1.0",
            },
            method="POST",
        )
        with request.urlopen(req, timeout=60) as resp:
            payload = json.loads(resp.read().decode("utf-8", "replace"))
        return str(payload.get("filename") or "").strip() or None

    def list_recipes(self) -> list[dict[str, Any]]:
        if not self.configured():
            return []
        status, payload = self._request("GET", f"/api/household/{self.household_id}/recipe")
        if status == 200 and isinstance(payload, list):
            return payload
        return []

    def find_existing(self, payload: dict[str, Any]) -> dict[str, Any] | None:
        want_source = str(payload.get("source") or "").strip().lower()
        want_name = str(payload.get("name") or "").strip().lower()
        for recipe in self.list_recipes():
            source = str(recipe.get("source") or "").strip().lower()
            name = str(recipe.get("name") or "").strip().lower()
            if want_source and source and source == want_source:
                return recipe
            if want_name and name == want_name:
                return recipe
        return None

    def _recipe_update_payload(self, existing: dict[str, Any], payload: dict[str, Any]) -> dict[str, Any]:
        uploaded_photo = self._upload_photo_from_url(payload.get("photo")) if payload.get("photo") else existing.get("photo")
        return {
            "name": payload.get("name") or existing.get("name"),
            "description": payload.get("description") or existing.get("description") or "",
            "source": payload.get("source") or existing.get("source"),
            "visibility": int(payload.get("visibility", existing.get("visibility", 0) or 0)),
            "items": payload.get("items") or [
                {
                    "name": item.get("name"),
                    "description": item.get("description", ""),
                    "optional": bool(item.get("optional", False)),
                }
                for item in (existing.get("items") or [])
            ],
            "tags": payload.get("tags") or [
                tag.get("name") if isinstance(tag, dict) else str(tag)
                for tag in (existing.get("tags") or [])
            ],
            "cook_time": payload.get("cook_time", existing.get("cook_time")),
            "prep_time": payload.get("prep_time", existing.get("prep_time")),
            "time": payload.get("time", existing.get("time")),
            "yields": payload.get("yields", existing.get("yields")),
            "photo": uploaded_photo or existing.get("photo"),
            "planned": bool(existing.get("planned", False)),
            "planned_cooking_dates": existing.get("planned_cooking_dates") or [],
            "planned_days": existing.get("planned_days") or [],
        }

    def update_recipe(self, recipe_id: int | str, payload: dict[str, Any], existing: dict[str, Any] | None = None) -> dict[str, Any]:
        if not self.configured():
            raise RuntimeError("KitchenOwl credentials are not configured.")
        if existing is None:
            status, existing = self._request("GET", f"/api/recipe/{recipe_id}")
            if status != 200 or not isinstance(existing, dict):
                return {"status": "error", "http_status": status, "recipe": existing}
        body = self._recipe_update_payload(existing, payload)
        status, updated = self._request("POST", f"/api/recipe/{recipe_id}", body=body, timeout=45)
        return {"status": "updated" if status == 200 else "error", "http_status": status, "recipe": updated}

    def create_recipe(self, payload: dict[str, Any]) -> dict[str, Any]:
        if not self.configured():
            raise RuntimeError("KitchenOwl credentials are not configured.")
        existing = self.find_existing(payload)
        if existing:
            if payload.get("photo") and not existing.get("photo"):
                refreshed = self.update_recipe(existing.get("id"), payload, existing=existing)
                if refreshed.get("status") == "updated":
                    return {"status": "updated", "recipe": refreshed.get("recipe")}
            return {"status": "duplicate", "recipe": existing}
        create_payload = dict(payload)
        if create_payload.get("photo"):
            create_payload["photo"] = self._upload_photo_from_url(create_payload["photo"])
        status, created = self._request("POST", f"/api/household/{self.household_id}/recipe", body=create_payload, timeout=45)
        return {"status": "created" if status == 200 else "error", "http_status": status, "recipe": created}


kitchenowl = KitchenOwlService()
