import asyncio
import json
import logging
import os
import random
from pathlib import Path, PurePosixPath
from urllib.parse import urlparse

import aiohttp
from aiogram import types

import config


logger = logging.getLogger(__name__)

CAPTIONS = [
    "Шо ты блять псюнчик решил свой подергать?",
    "Решил подрочить сука?",
    "гнойный писюн у тебя кнч, да",
]

LESAINT_POSTS_API = "https://lesaintdesseins.fr/wp-json/wp/v2/posts"
LESAINT_REFERER = "https://lesaintdesseins.fr/"
REQUEST_HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/124.0.0.0 Safari/537.36"
    ),
    "Accept": "application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Referer": LESAINT_REFERER,
}

_NUDE_HISTORY_LOCK = asyncio.Lock()


def _resolve_int_setting(name: str, default: int) -> int:
    value = getattr(config, name, None)
    if value is None:
        value = os.getenv(name, str(default))

    try:
        return int(value)
    except (TypeError, ValueError):
        logger.warning("Invalid %s=%r, using default %s", name, value, default)
        return default


def _resolve_path_setting(name: str, default: str) -> Path:
    value = getattr(config, name, None)
    if value is None:
        value = os.getenv(name, default)

    if isinstance(value, Path):
        return value

    return Path(str(value))


NUDE_HISTORY_PATH = _resolve_path_setting("NUDE_HISTORY_PATH", "/db/nude_history.json")
NUDE_POSTS_PER_PAGE = _resolve_int_setting("NUDE_POSTS_PER_PAGE", 100)
NUDE_POSTS_POOL_TARGET = _resolve_int_setting("NUDE_POSTS_POOL_TARGET", 1200)
NUDE_MAX_POSTS_TO_SCAN = _resolve_int_setting("NUDE_MAX_POSTS_TO_SCAN", 2000)
NUDE_MIN_UNSEEN_POOL = _resolve_int_setting("NUDE_MIN_UNSEEN_POOL", 100)
NUDE_HISTORY_MAX_SIZE = _resolve_int_setting("NUDE_HISTORY_MAX_SIZE", 5000)


def _extract_photo_candidates(posts: list[dict]) -> list[dict]:
    candidates = []
    seen_urls = set()

    for post in posts:
        embedded = post.get("_embedded") or {}
        media_items = embedded.get("wp:featuredmedia") or []

        for media in media_items:
            if media.get("media_type") != "image":
                continue

            media_details = media.get("media_details") or {}
            sizes = media_details.get("sizes") or {}
            image_url = (
                (sizes.get("full") or {}).get("source_url")
                or media.get("source_url")
            )

            if not image_url or image_url in seen_urls:
                continue

            seen_urls.add(image_url)
            candidates.append(
                {
                    "image_url": image_url,
                    "post_url": post.get("link", LESAINT_REFERER),
                    "title": (post.get("title") or {}).get("rendered", "").strip(),
                }
            )
            break

    return candidates


def _guess_filename(image_url: str) -> str:
    parsed = urlparse(image_url)
    filename = PurePosixPath(parsed.path).name or "nude.jpg"

    if "." not in filename:
        filename = f"{filename}.jpg"

    return filename


def _load_nude_history() -> dict:
    path = NUDE_HISTORY_PATH
    if not path.exists():
        return {"sent_images": []}

    try:
        with path.open("r", encoding="utf-8") as fh:
            data = json.load(fh)
    except (OSError, json.JSONDecodeError):
        logger.exception("Failed to load nude history")
        return {"sent_images": []}

    if not isinstance(data, dict):
        return {"sent_images": []}

    sent_images = data.get("sent_images")
    if not isinstance(sent_images, list):
        sent_images = []

    cleaned = [item for item in sent_images if isinstance(item, str) and item.strip()]
    return {"sent_images": cleaned[-NUDE_HISTORY_MAX_SIZE :]}


def _save_nude_history(data: dict) -> None:
    path = NUDE_HISTORY_PATH
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = path.with_name(f"{path.name}.tmp")
    with tmp_path.open("w", encoding="utf-8") as fh:
        json.dump(data, fh, ensure_ascii=False, indent=2)
    tmp_path.replace(path)


def _remember_sent_image(history: dict, image_url: str) -> None:
    sent_images = history.get("sent_images")
    if not isinstance(sent_images, list):
        sent_images = []

    sent_images = [item for item in sent_images if item != image_url]
    sent_images.append(image_url)
    history["sent_images"] = sent_images[-NUDE_HISTORY_MAX_SIZE :]


async def _fetch_posts_page(
    session: aiohttp.ClientSession, page: int, per_page: int
) -> tuple[list[dict], int | None]:
    params = {
        "page": str(page),
        "per_page": str(per_page),
        "orderby": "date",
        "order": "desc",
        "_embed": "wp:featuredmedia",
    }

    async with session.get(LESAINT_POSTS_API, params=params, headers=REQUEST_HEADERS) as resp:
        if resp.status == 400 and page > 1:
            body = await resp.text()
            if "rest_post_invalid_page_number" in body:
                return [], None
            raise RuntimeError(f"API returned 400: {body[:300]}")

        if resp.status != 200:
            body = await resp.text()
            raise RuntimeError(f"API returned {resp.status}: {body[:300]}")

        total_pages_header = resp.headers.get("X-WP-TotalPages")
        total_pages = (
            int(total_pages_header)
            if total_pages_header and total_pages_header.isdigit()
            else None
        )
        posts = await resp.json(content_type=None)

    if not isinstance(posts, list):
        raise RuntimeError("Unexpected API response format")

    return posts, total_pages


async def _fetch_latest_photo_candidates(
    session: aiohttp.ClientSession, sent_urls: set[str]
) -> list[dict]:
    per_page = max(1, min(NUDE_POSTS_PER_PAGE, 100))
    max_scan = max(NUDE_POSTS_POOL_TARGET, NUDE_MAX_POSTS_TO_SCAN)
    max_pages = max(1, (max_scan + per_page - 1) // per_page)

    candidates = []
    seen_urls = set()
    unseen_count = 0
    total_pages = None

    for page in range(1, max_pages + 1):
        if total_pages is not None and page > total_pages:
            break

        posts, reported_total_pages = await _fetch_posts_page(session, page, per_page)
        if reported_total_pages is not None:
            total_pages = reported_total_pages
        if not posts:
            break

        for candidate in _extract_photo_candidates(posts):
            image_url = candidate["image_url"]
            if image_url in seen_urls:
                continue

            seen_urls.add(image_url)
            candidates.append(candidate)

            if image_url not in sent_urls:
                unseen_count += 1

        if len(candidates) >= NUDE_POSTS_POOL_TARGET and unseen_count >= NUDE_MIN_UNSEEN_POOL:
            break
        if len(candidates) >= max_scan:
            break

    return candidates


def _pick_unsent_candidate(candidates: list[dict], sent_urls: set[str]) -> dict | None:
    unseen = [candidate for candidate in candidates if candidate["image_url"] not in sent_urls]
    if not unseen:
        return None
    return random.choice(unseen)


async def _download_photo(
    session: aiohttp.ClientSession, image_url: str
) -> tuple[bytes | None, str]:
    async with session.get(image_url, headers=REQUEST_HEADERS) as resp:
        if resp.status != 200:
            logger.warning("Image download failed for %s: %s", image_url, resp.status)
            return None, _guess_filename(image_url)

        content_type = resp.headers.get("Content-Type", "")
        if "image" not in content_type.lower():
            logger.warning(
                "Unexpected content type for %s: %s", image_url, content_type
            )
            return None, _guess_filename(image_url)

        return await resp.read(), _guess_filename(image_url)


async def handle_nude_cmd(message: types.Message):
    args = message.text.split(maxsplit=1)

    if len(args) > 1 and args[1].strip() == "-h":
        help_msg = (
            "🔥 <b>Справка по команде /nude:</b>\n\n"
            "Берет случайную фотку из последних постов сайта le saint des seins.\n"
            "• Источник: <code>https://lesaintdesseins.fr/</code>\n"
            "• Пагинацией тянет 1000+ последних постов через WordPress REST API\n"
            "• Хранит историю отправок и старается не повторять фотки\n"
            "• Выбирает случайную картинку и отправляет ее прямо в Telegram\n\n"
            "<i>Пример:</i> <code>/nude</code>"
        )
        await message.reply(help_msg, parse_mode="HTML")
        return

    timeout = aiohttp.ClientTimeout(total=60)
    history_snapshot = _load_nude_history()
    sent_urls_snapshot = set(history_snapshot["sent_images"])

    try:
        async with aiohttp.ClientSession(timeout=timeout) as session:
            candidates = await _fetch_latest_photo_candidates(session, sent_urls_snapshot)

            if not candidates:
                await message.reply("Не удалось найти свежие фотки :(")
                return

            async with _NUDE_HISTORY_LOCK:
                history = _load_nude_history()
                sent_urls = set(history["sent_images"])
                selected = _pick_unsent_candidate(candidates, sent_urls)

                if selected is None:
                    logger.info("Nude history exhausted current pool, resetting history")
                    history = {"sent_images": []}
                    selected = random.choice(candidates)

                _remember_sent_image(history, selected["image_url"])
                _save_nude_history(history)

            caption = random.choice(CAPTIONS)
            image_bytes, filename = await _download_photo(session, selected["image_url"])

            if image_bytes:
                photo = types.BufferedInputFile(image_bytes, filename=filename)
                await message.answer_photo(photo=photo, caption=caption)
                return

            try:
                photo = types.URLInputFile(selected["image_url"])
                await message.answer_photo(photo=photo, caption=caption)
            except Exception:
                logger.exception("Failed to send photo by URL")
                await message.answer(
                    f"{caption}\n{selected['image_url']}\n{selected['post_url']}"
                )

    except Exception:
        logger.exception("Error in nude command")
        await message.reply("Произошла ошибка при получении фото :(")
