"""Todoist REST API integration service."""

from __future__ import annotations

import logging
import os
from typing import Any

import requests
from requests import Response
from requests.exceptions import RequestException

from app.core.config import settings
from app.services import runtime_config

TODOIST_API_BASE = "https://api.todoist.com/api/v1"
REQUEST_TIMEOUT_SECONDS = 20
logger = logging.getLogger(__name__)
DEBUG_ENABLED = os.getenv("DEBUG", "").strip().lower() in {"1", "true", "yes", "on"}


class TodoistConfigError(RuntimeError):
    """Raised when required Todoist configuration is missing."""


class TodoistAPIError(RuntimeError):
    """Raised when Todoist API returns an error response."""

    def __init__(self, status_code: int, detail: str):
        super().__init__(detail)
        self.status_code = status_code
        self.detail = detail


def _get_token() -> str:
    """Return Todoist API token or raise configuration error."""
    token = runtime_config.get_effective_todoist_token(fallback=settings.todoist_api_token)
    if not token:
        raise TodoistConfigError("TODOIST_API_TOKEN is not configured")
    return token


def _headers() -> dict[str, str]:
    """Build standard Todoist request headers."""
    return {
        "Authorization": f"Bearer {_get_token()}",
        "Content-Type": "application/json",
    }


def _parse_api_error(response: Response) -> str:
    """Extract human-readable error details from Todoist response."""
    if response.status_code == 401:
        return "Todoist authentication failed. Verify TODOIST_API_TOKEN."
    if response.status_code == 403:
        return "Todoist access denied for this token."

    try:
        payload = response.json()
    except ValueError:
        text = response.text.strip()
        return text or "Todoist API request failed"

    if isinstance(payload, dict):
        for key in ("error", "message", "detail"):
            value = payload.get(key)
            if isinstance(value, str) and value.strip():
                return value.strip()
    return "Todoist API request failed"


def _raise_for_api_error(response: Response) -> None:
    """Raise a normalized Todoist error for non-success responses."""
    if 200 <= response.status_code < 300:
        return
    raise TodoistAPIError(response.status_code, _parse_api_error(response))


def _payload_preview(payload: Any, max_len: int = 240) -> str:
    """Return safe, compact preview for unexpected payload errors."""
    raw = repr(payload)
    if len(raw) <= max_len:
        return raw
    return f"{raw[:max_len]}..."


def _request(
    method: str,
    path: str,
    json: dict[str, Any] | None = None,
    params: dict[str, Any] | None = None,
) -> Response:
    """Execute a Todoist API request with normalized error handling."""
    url = f"{TODOIST_API_BASE}{path}"
    try:
        response = requests.request(
            method=method,
            url=url,
            headers=_headers(),
            json=json,
            params=params,
            timeout=REQUEST_TIMEOUT_SECONDS,
        )
    except RequestException as exc:
        raise TodoistAPIError(status_code=502, detail=f"Todoist request failed: {exc}") from exc
    return response


def create_task(
    content: str,
    description: str | None = None,
    due_string: str | None = None,
) -> dict[str, Any]:
    """Create one task in Todoist."""
    payload: dict[str, Any] = {"content": content}
    if description is not None:
        payload["description"] = description
    if due_string:
        payload["due_string"] = due_string

    response = _request("POST", "/tasks", json=payload)
    _raise_for_api_error(response)
    return response.json()


def list_tasks() -> list[dict[str, Any]]:
    """Return all open Todoist tasks using API v1 pagination."""
    all_tasks: list[dict[str, Any]] = []
    cursor: str | None = None

    while True:
        params: dict[str, Any] = {}
        if cursor:
            params["cursor"] = cursor

        response = _request("GET", "/tasks", params=params or None)
        if DEBUG_ENABLED:
            logger.info("Todoist list_tasks url=%s status=%s", response.url, response.status_code)

        if response.status_code != 200:
            try:
                response.raise_for_status()
            except RequestException as exc:
                detail = _parse_api_error(response)
                raise TodoistAPIError(
                    response.status_code,
                    f"Todoist list_tasks failed ({response.status_code}): {detail}",
                ) from exc

        try:
            payload = response.json()
        except ValueError as exc:
            raise TodoistAPIError(
                502,
                "Unexpected Todoist response format: invalid JSON from /tasks",
            ) from exc

        if isinstance(payload, list):
            if DEBUG_ENABLED:
                logger.info("Todoist list_tasks payload_type=list count=%s", len(payload))
            all_tasks.extend([item for item in payload if isinstance(item, dict)])
            break

        if isinstance(payload, dict):
            results = payload.get("results")
            if not isinstance(results, list):
                raise TodoistAPIError(
                    502,
                    f"Unexpected Todoist response format (expected list or dict.results): {_payload_preview(payload)}",
                )

            if DEBUG_ENABLED:
                logger.info("Todoist list_tasks payload_type=dict.results count=%s", len(results))
            all_tasks.extend([item for item in results if isinstance(item, dict)])
            next_cursor = payload.get("next_cursor")
            if isinstance(next_cursor, str) and next_cursor.strip():
                cursor = next_cursor
                continue
            break

        raise TodoistAPIError(
            502,
            f"Unexpected Todoist response format (expected list or dict): {_payload_preview(payload)}",
        )

    if DEBUG_ENABLED:
        logger.info("Todoist list_tasks total_count=%s", len(all_tasks))
    return all_tasks


def complete_task(task_id: str | int) -> None:
    """Mark one Todoist task as complete."""
    response = _request("POST", f"/tasks/{task_id}/close")
    _raise_for_api_error(response)
