"""History router.

Provides endpoints for forensic exploration of all historical ban records
stored in the fail2ban SQLite database.

Routes
------
``GET /api/history``
    Paginated list of all historical bans, filterable by jail, IP prefix, and
    time range.

``GET /api/history/{ip}``
    Per-IP detail: complete ban timeline, aggregated totals, and geolocation.
"""

from __future__ import annotations

from typing import Literal

from fastapi import APIRouter, Query, Request

from app.dependencies import (
    AuthDep,
    Fail2BanMetadataServiceDep,
    Fail2BanSocketDep,
    HistoryServiceContextDep,
    HttpSessionDep,
    SettingsDep,
)
from app.exceptions import HistoryNotFoundError
from app.mappers import history_mappers
from app.models._common import TimeRange
from app.models.ban import BanOrigin
from app.models.history import HistoryListResponse, IpDetailResponse
from app.services import history_service
from app.utils.constants import DEFAULT_PAGE_SIZE

router: APIRouter = APIRouter(prefix="/api/v1/history", tags=["History"])


@router.get(
    "",
    response_model=HistoryListResponse,
    summary="Return a paginated list of historical bans",
    responses={
        200: {"description": "History list returned", "model": HistoryListResponse},
        401: {"description": "Session missing, expired, or invalid"},
        502: {"description": "fail2ban unreachable"},
    },
)
async def get_history(
    request: Request,
    _auth: AuthDep,
    history_ctx: HistoryServiceContextDep,
    socket_path: Fail2BanSocketDep,
    http_session: HttpSessionDep,
    fail2ban_metadata_service: Fail2BanMetadataServiceDep,
    settings: SettingsDep,
    range: TimeRange | None = Query(
        default=None,
        description="Optional time-range filter. Omit for all-time.",
    ),
    jail: str | None = Query(
        default=None,
        description="Restrict results to this jail name.",
    ),
    ip: str | None = Query(
        default=None,
        description="Restrict results to IPs matching this prefix.",
    ),
    origin: BanOrigin | None = Query(
        default=None,
        description="Filter by ban origin: 'blocklist' or 'selfblock'. Omit for all.",
    ),
    source: Literal["fail2ban", "archive"] = Query(
        default="fail2ban",
        description="Data source: 'fail2ban' or 'archive'.",
    ),
    page: int = Query(default=1, ge=1, description="1-based page number."),
    page_size: int = Query(
        default=DEFAULT_PAGE_SIZE,
        ge=1,
        description="Items per page.",
    ),
) -> HistoryListResponse:
    """Return a paginated list of historical bans with optional filters.

    Queries the fail2ban database for all ban records, applying the requested
    filters.  Results are ordered newest-first and enriched with geolocation.

    Args:
        request: The incoming request (used to access ``app.state``).
        _auth: Validated session — enforces authentication.
        history_ctx: History service context containing db and repositories.
        socket_path: Path to fail2ban Unix domain socket.
        http_session: Shared HTTP session for geolocation.
        fail2ban_metadata_service: Fail2Ban metadata service.
        range: Optional time-range preset.  ``None`` means all-time.
        jail: Optional jail name filter (exact match).
        ip: Optional IP prefix filter (prefix match).
        page: 1-based page number.
        page_size: Items per page (1–500).

    Returns:
        :class:`~app.models.history.HistoryListResponse` with paginated items
        and the total matching count.
    """

    domain_result = await history_service.list_history(
        socket_path,
        range_=range,
        jail=jail,
        ip_filter=ip,
        origin=origin,
        source=source,
        page=page,
        page_size=page_size,
        max_page_size=settings.max_page_size,
        http_session=http_session,
        db=history_ctx.db,
        fail2ban_metadata_service=fail2ban_metadata_service,
    )
    return history_mappers.map_domain_history_list_to_response(domain_result)


@router.get(
    "/archive",
    response_model=HistoryListResponse,
    summary="Return a paginated list of archived historical bans",
    responses={
        200: {"description": "Archived history list returned", "model": HistoryListResponse},
        401: {"description": "Session missing, expired, or invalid"},
        502: {"description": "fail2ban unreachable"},
    },
)
async def get_history_archive(
    request: Request,
    _auth: AuthDep,
    history_ctx: HistoryServiceContextDep,
    socket_path: Fail2BanSocketDep,
    http_session: HttpSessionDep,
    fail2ban_metadata_service: Fail2BanMetadataServiceDep,
    settings: SettingsDep,
    range: TimeRange | None = Query(
        default=None,
        description="Optional time-range filter. Omit for all-time.",
    ),
    jail: str | None = Query(default=None, description="Restrict results to this jail name."),
    ip: str | None = Query(default=None, description="Restrict results to IPs matching this prefix."),
    page: int = Query(default=1, ge=1, description="1-based page number."),
    page_size: int = Query(default=DEFAULT_PAGE_SIZE, ge=1, description="Items per page."),
) -> HistoryListResponse:

    domain_result = await history_service.list_history(
        socket_path,
        range_=range,
        jail=jail,
        ip_filter=ip,
        source="archive",
        page=page,
        page_size=page_size,
        max_page_size=settings.max_page_size,
        http_session=http_session,
        db=history_ctx.db,
        fail2ban_metadata_service=fail2ban_metadata_service,
    )
    return history_mappers.map_domain_history_list_to_response(domain_result)


@router.get(
    "/{ip}",
    response_model=IpDetailResponse,
    summary="Return the full ban history for a single IP address",
    responses={
        200: {"description": "IP history detail returned", "model": IpDetailResponse},
        401: {"description": "Session missing, expired, or invalid"},
        404: {"description": "No history found for this IP"},
        502: {"description": "fail2ban unreachable"},
    },
)
async def get_ip_history(
    request: Request,
    _auth: AuthDep,
    ip: str,
    socket_path: Fail2BanSocketDep,
    http_session: HttpSessionDep,
    fail2ban_metadata_service: Fail2BanMetadataServiceDep,
) -> IpDetailResponse:
    """Return the complete historical record for a single IP address.

    Fetches all ban events for the given IP from the fail2ban database and
    aggregates them into a timeline.  Returns ``404`` if the IP has no
    recorded history.

    Args:
        request: The incoming request.
        _auth: Validated session dependency.
        ip: The IP address to look up.

    Returns:
        :class:`~app.models.history.IpDetailResponse` with aggregated totals
        and a full ban timeline.

    Raises:
        HTTPException: 404 if the IP has no history in the database.
    """

    domain_result = await history_service.get_ip_detail(
        socket_path,
        ip,
        http_session=http_session,
        fail2ban_metadata_service=fail2ban_metadata_service,
    )

    if domain_result is None:
        raise HistoryNotFoundError(ip)

    return history_mappers.map_domain_ip_detail_to_response(domain_result)
