import json
import logging
from typing import TYPE_CHECKING, Optional

import requests
from bs4 import BeautifulSoup
from django.conf import settings
from django.contrib.auth import get_user_model

User = get_user_model()
if TYPE_CHECKING:
    from scrobbles.models import Scrobble

logger = logging.getLogger(__name__)

SEARCH_ID_URL = "https://boardgamegeek.com/xmlapi/search?search={query}&exact=1"
GAME_ID_URL = "https://boardgamegeek.com/xmlapi/boardgame/{id}"
BGG_ACCESS_TOKEN = getattr(settings, "BGG_ACCESS_TOKEN", "")
BASE_HEADERS = {
    "User-Agent": "Vrobbler 31.0",
    "Authorization": f"Bearer {BGG_ACCESS_TOKEN}",
}


def take_first(thing: Optional[list]) -> str:
    first = ""
    try:
        first = thing[0]
    except IndexError:
        pass

    if first:
        try:
            first = first.get_text()
        except:
            pass

    return first


def lookup_boardgame_id_from_bgg(title: str) -> Optional[int]:
    soup = None
    game_id = None
    url = SEARCH_ID_URL.format(query=title)
    r = requests.get(url, headers=BASE_HEADERS)
    if r.status_code == 200:
        soup = BeautifulSoup(r.text, "xml")

    if soup:
        result = soup.findAll("boardgame")
        if not result:
            return game_id

        game_id = result[0].get("objectid", None)

    return game_id


def lookup_boardgame_from_bgg(lookup_id: str) -> dict:
    soup = None
    game_dict = {}

    title = ""
    bgg_id = None

    try:
        bgg_id = int(lookup_id)
        logger.debug(f"Using BGG ID {bgg_id} to find board game")
    except ValueError:
        title = lookup_id
        logger.debug(f"Using title {title} to find board game")

    if not bgg_id:
        bgg_id = lookup_boardgame_id_from_bgg(title)

    url = GAME_ID_URL.format(id=bgg_id)
    r = requests.get(url, headers=BASE_HEADERS)
    if r.status_code == 200:
        soup = BeautifulSoup(r.text, "xml")

    if soup:
        seconds_to_play = None
        minutes = take_first(soup.findAll("playingtime"))
        if minutes:
            seconds_to_play = int(minutes) * 60

        game_dict = {
            "bggeek_id": bgg_id,
            "title": take_first(soup.findAll("name", primary="true")),
            "description": take_first(soup.findAll("description")),
            "year_published": take_first(soup.findAll("yearpublished")),
            "publisher_name": take_first(soup.findAll("boardgamepublisher")),
            "cover_url": take_first(soup.findAll("image")),
            "min_players": take_first(soup.findAll("minplayers")),
            "max_players": take_first(soup.findAll("maxplayers")),
            "recommended_age": take_first(soup.findAll("age")),
            "run_time_seconds": seconds_to_play,
        }

    return game_dict


def fetch_existing_bgg_plays(bgg_username: str) -> list[dict]:
    """Fetch recent plays from BGG for a user to check for duplicates."""
    url = f"https://boardgamegeek.com/xmlapi2/plays?username={bgg_username}&page=1"
    r = requests.get(url, headers=BASE_HEADERS)
    if r.status_code != 200:
        logger.error(
            "Failed to fetch BGG plays",
            extra={"status_code": r.status_code, "username": bgg_username},
        )
        return []

    soup = BeautifulSoup(r.text, "xml")
    plays = []
    for play_elem in soup.findAll("play"):
        item = play_elem.find("item")
        if item:
            plays.append(
                {
                    "objectid": item.get("objectid"),
                    "date": play_elem.get("date"),
                    "quantity": play_elem.get("quantity"),
                    "length": play_elem.get("length"),
                }
            )
    return plays


def is_duplicate_bgg_play(
    existing_plays: list[dict], bgg_id: str, play_date: str
) -> bool:
    """Check if a play already exists on BGG matching game ID and date."""
    for play in existing_plays:
        if play["objectid"] == str(bgg_id) and play["date"] == play_date:
            return True
    return False


def push_scrobble_to_bgg(scrobble: "Scrobble", user: User) -> Optional[bool]:
    from boardgames.models import BoardGameLocation
    from people.models import Person

    bgg_username = user.profile.bgg_username if hasattr(user, "profile") else None
    bgg_password = getattr(settings, "BGG_PASSWORD", "")

    if not bgg_username or not bgg_password:
        logger.warning(
            "Cannot push to BGG: missing credentials",
            extra={"user_id": user.id, "bgg_username": bgg_username},
        )
        return None

    play_date = scrobble.timestamp.date().strftime("%Y-%m-%d")
    bgg_id = scrobble.media_obj.bggeek_id

    existing_plays = fetch_existing_bgg_plays(bgg_username)
    if is_duplicate_bgg_play(existing_plays, bgg_id, play_date):
        logger.info(
            "Skipping duplicate BGG play",
            extra={
                "bgg_id": bgg_id,
                "play_date": play_date,
                "scrobble_id": scrobble.id,
            },
        )
        return None

    login_payload = {
        "credentials": {"username": bgg_username, "password": bgg_password}
    }
    headers = BASE_HEADERS.copy()
    headers["content-type"] = "application/json"

    with requests.Session() as s:
        p = s.post(
            "https://boardgamegeek.com/login/api/v1",
            data=json.dumps(login_payload),
            headers=headers,
        )
        if p.status_code != 200:
            logger.error(
                "BGG login failed",
                extra={"status_code": p.status_code, "response": p.text},
            )
            return None

        players = []
        if scrobble.log:
            for player in scrobble.log.get("players", []):
                player_person = Person.objects.filter(
                    id=player.get("person_id")
                ).first()
                if not player_person:
                    continue

                player_data = {
                    "name": player_person.name,
                    "win": player.get("win", False),
                    "new": player.get("new", False),
                    "score": player.get("score"),
                }
                if player_person.bgg_username:
                    player_data["username"] = player_person.bgg_username
                players.append(player_data)

        location = None
        if scrobble.log and scrobble.log.get("location_id"):
            boardgame_location = BoardGameLocation.objects.filter(
                id=scrobble.log["location_id"]
            ).first()
            if boardgame_location:
                location = boardgame_location.name

        play_payload = {
            "playdate": play_date,
            "length": (scrobble.playback_position_seconds or 0) / 60,
            "comments": "Uploaded from Vrobbler",
            "location": location,
            "objectid": bgg_id,
            "quantity": "1",
            "action": "save",
            "players": players,
            "objecttype": "thing",
            "ajax": 1,
        }
        r = s.post(
            "https://boardgamegeek.com/geekplay.php",
            data=json.dumps(play_payload),
            headers=headers,
        )
        return r.status_code == 200
