"""Base command class for CLI commands."""

from __future__ import annotations

from abc import ABC, abstractmethod
import argparse
from configparser import ConfigParser
from multiprocessing import Event
import sys
from threading import Thread
from typing import Sequence, TypeVar

from cli115.auth.cookie import CookieAuth
from cli115.client import Client, create_client
from cli115.credentials import CredentialManager
from cli115.exceptions import CommandLineError

T = TypeVar("T")


class BaseCommand(ABC):
    """Abstract base for all CLI commands."""

    def __init__(
        self,
        config: ConfigParser | None = None,
        credential_manager: CredentialManager | None = None,
    ) -> None:
        self.cfg = config
        self.cm = credential_manager

    def register(self, parser: argparse.ArgumentParser) -> None:
        """Register arguments on the given parser."""

    @abstractmethod
    def execute(self, args: argparse.Namespace) -> None:
        """Execute the command with parsed arguments."""

    def warn(self, message: str) -> None:
        print(f"Warning: {message}", file=sys.stderr)

    def _create_client(
        self, uid: str | None = None, cred_type: str | None = None
    ) -> Client:
        if uid:
            cred_type, cred = self.cm.get_credential(uid, cred_type)
        else:
            cred_type, cred = self.cm.current_credential
        if cred_type == "cookie":
            auth = CookieAuth(
                uid=cred["UID"],
                cid=cred["CID"],
                seid=cred["SEID"],
                kid=cred["KID"],
            )
            return create_client(auth)
        else:
            raise CommandLineError(f"unsupported credential type: {cred_type}")


class PaginationCommand(BaseCommand, ABC):
    """Base class for commands that support --limit / --offset pagination.

    Subclasses call :meth:`apply_pagination` with a :class:`Sequence`
    to get the slice of items to display.  When no ``--offset`` is given and
    the collection has more items than ``--limit``, a warning is printed to
    *stderr*.
    """

    _default_page_size: int = 30

    def register(self, parser: argparse.ArgumentParser) -> None:
        super().register(parser)
        parser.add_argument(
            "--limit",
            type=int,
            default=None,
            help=f"Maximum number of items to show (default: {self._default_page_size})",
        )
        parser.add_argument(
            "--offset",
            type=int,
            default=None,
            help="Number of items to skip (pagination offset)",
        )

    def apply_pagination(
        self,
        collection: Sequence[T],
        args: argparse.Namespace,
    ) -> list[T]:
        """Return a sliced list from *collection* according to --limit/--offset.

        If ``--offset`` is not provided and the collection has more items than
        ``--limit``, a warning is printed to *stderr*. Pass ``user_limit`` as
        the value of ``args.limit`` *before* any command-level default is
        applied, so the warning is suppressed when the user explicitly provided
        ``--limit``.
        """
        offset = args.offset if args.offset is not None else 0
        limit = args.limit or self._default_page_size

        items = list(collection[offset : offset + limit])
        total = len(collection)

        if not args.offset and not args.limit and total > limit:
            self.warn(
                (
                    "Warning: {total} items total, only showing up to {limit}. "
                    "Use --offset and --limit to paginate."
                ).format(total=total, limit=limit)
            )
        return items


class MultiCommand(BaseCommand, ABC):
    """Base class for commands that dispatch to named subcommands."""

    subcommands: list[tuple[str, type[BaseCommand]]] = []

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._subcmd_instances: dict[str, BaseCommand] = {
            name: cls(*args, **kwargs) for name, cls in self.subcommands
        }

    def register(self, parser: argparse.ArgumentParser) -> None:
        sub = parser.add_subparsers(dest="action", required=True)
        for name, cmd in self.subcommands:
            sub_parser = sub.add_parser(name, help=cmd.__doc__)
            self._subcmd_instances[name].register(sub_parser)

    def execute(self, args: argparse.Namespace) -> None:
        self._subcmd_instances[args.action].execute(args)


class WorkerCommand(BaseCommand, ABC):
    @abstractmethod
    def worker(self, args: argparse.Namespace):
        pass

    def run_worker(self, args: argparse.Namespace):
        done = Event()
        state: dict[str, object] = {}

        def worker():
            try:
                state["result"] = self.worker(args)
            except BaseException as exc:
                state["error"] = exc
            finally:
                done.set()

        Thread(target=worker, daemon=True).start()

        try:
            while not done.wait(timeout=0.1):
                pass
        except KeyboardInterrupt as exc:
            raise CommandLineError("process cancelled by user") from exc

        if "error" in state:
            raise state["error"]
        return state["result"]
