"""Admin-only Visitor Monitor endpoints.

Mounted at ``/api/v1/admin/visitor-monitor``. All routes require
``get_admin_user``. Mutating routes also write an audit-log entry.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Optional

from core.database import get_db
from dependencies.auth import get_admin_user
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from models.visitor_monitor import Campaign
from schemas.auth import UserResponse
from schemas.visitor_monitor import (
    CampaignCreate,
    CampaignItem,
    CampaignUpdate,
    JourneyResponse,
    OverviewStats,
    ReportResponse,
    SessionListResponse,
    TrackingSettingsOut,
    TrackingSettingsUpdate,
)
from services import audit_log as audit_log_service
from services.visitor_monitor import (
    campaign_to_item,
    journey as svc_journey,
    list_campaigns as svc_list_campaigns,
    list_sessions as svc_list_sessions,
    overview_stats as svc_overview_stats,
    report as svc_report,
    settings_to_dto,
    get_settings as svc_get_settings,
    update_settings as svc_update_settings,
)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

router = APIRouter(
    prefix="/api/v1/admin/visitor-monitor", tags=["admin-visitor-monitor"]
)


# Audit action identifiers for this module.
ACTION_CAMPAIGN_CREATED = "admin.visitor_monitor.campaign_created"
ACTION_CAMPAIGN_UPDATED = "admin.visitor_monitor.campaign_updated"
ACTION_CAMPAIGN_DELETED = "admin.visitor_monitor.campaign_deleted"
ACTION_SETTINGS_UPDATED = "admin.visitor_monitor.settings_updated"


def _parse_iso(value: Optional[str], field: str) -> Optional[datetime]:
    if not value:
        return None
    raw = value.strip()
    if raw.endswith("Z"):
        raw = raw[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(raw)
    except ValueError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"{field} must be an ISO-8601 datetime",
        )
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    return parsed


def _base_url(request: Request) -> str:
    """Build the public site base URL from the incoming request."""
    origin = request.headers.get("origin")
    if origin:
        return origin.rstrip("/")
    host = request.headers.get("x-forwarded-host") or request.headers.get("host")
    scheme = request.headers.get("x-forwarded-proto") or request.url.scheme
    if host:
        return f"{scheme}://{host}".rstrip("/")
    return str(request.base_url).rstrip("/")


# ---------------------------------------------------------------------------
# Overview
# ---------------------------------------------------------------------------


@router.get("/overview", response_model=OverviewStats)
async def get_overview(
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> OverviewStats:
    return await svc_overview_stats(db)


# ---------------------------------------------------------------------------
# Sessions / Live
# ---------------------------------------------------------------------------


@router.get("/sessions", response_model=SessionListResponse)
async def list_sessions_endpoint(
    only_online: bool = Query(default=False),
    search: Optional[str] = Query(default=None),
    country: Optional[str] = Query(default=None),
    device: Optional[str] = Query(default=None),
    utm_campaign: Optional[str] = Query(default=None),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=50, ge=1, le=200),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> SessionListResponse:
    items, total = await svc_list_sessions(
        db,
        only_online=only_online,
        search=search,
        country=country,
        device=device,
        utm_campaign=utm_campaign,
        page=page,
        page_size=page_size,
    )
    return SessionListResponse(items=items, total=total, page=page, page_size=page_size)


@router.get("/sessions/{session_id}", response_model=JourneyResponse)
async def get_journey(
    session_id: str,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> JourneyResponse:
    result = await svc_journey(db, session_id)
    if result is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")
    return result


# ---------------------------------------------------------------------------
# Reports
# ---------------------------------------------------------------------------


@router.get("/reports", response_model=ReportResponse)
async def reports_endpoint(
    start: Optional[str] = Query(default=None),
    end: Optional[str] = Query(default=None),
    campaign: Optional[str] = Query(default=None),
    source: Optional[str] = Query(default=None),
    country: Optional[str] = Query(default=None),
    device: Optional[str] = Query(default=None),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> ReportResponse:
    return await svc_report(
        db,
        start=_parse_iso(start, "start"),
        end=_parse_iso(end, "end"),
        campaign=campaign,
        source=source,
        country=country,
        device=device,
    )


# ---------------------------------------------------------------------------
# Campaigns
# ---------------------------------------------------------------------------


@router.get("/campaigns", response_model=list[CampaignItem])
async def list_campaigns_endpoint(
    request: Request,
    status_filter: Optional[str] = Query(default=None, alias="status"),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> list[CampaignItem]:
    return await svc_list_campaigns(db, base_url=_base_url(request), status_filter=status_filter)


@router.post("/campaigns", response_model=CampaignItem, status_code=status.HTTP_201_CREATED)
async def create_campaign(
    payload: CampaignCreate,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CampaignItem:
    row = Campaign(
        name=payload.name,
        utm_source=payload.utm_source,
        utm_medium=payload.utm_medium,
        utm_campaign=payload.utm_campaign,
        utm_term=payload.utm_term,
        utm_content=payload.utm_content,
        landing_page=payload.landing_page or "/",
        target_url=payload.target_url,
        start_date=payload.start_date,
        end_date=payload.end_date,
        status=payload.status or "active",
        notes=payload.notes,
    )
    db.add(row)
    await db.commit()
    await db.refresh(row)
    await audit_log_service.record_admin_event(
        db,
        action=ACTION_CAMPAIGN_CREATED,
        admin_user_id=admin.id,
        admin_email=admin.email,
        details={"campaign_id": row.id, "name": row.name},
        request=request,
    )
    return await campaign_to_item(db, row, _base_url(request))


@router.patch("/campaigns/{campaign_id}", response_model=CampaignItem)
async def update_campaign(
    campaign_id: int,
    payload: CampaignUpdate,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CampaignItem:
    res = await db.execute(select(Campaign).where(Campaign.id == campaign_id))
    row = res.scalar_one_or_none()
    if row is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Campaign not found")
    for field, value in payload.model_dump(exclude_unset=True).items():
        setattr(row, field, value)
    await db.commit()
    await db.refresh(row)
    await audit_log_service.record_admin_event(
        db,
        action=ACTION_CAMPAIGN_UPDATED,
        admin_user_id=admin.id,
        admin_email=admin.email,
        details={"campaign_id": row.id, "changes": payload.model_dump(exclude_unset=True)},
        request=request,
    )
    return await campaign_to_item(db, row, _base_url(request))


@router.delete("/campaigns/{campaign_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_campaign(
    campaign_id: int,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
):
    res = await db.execute(select(Campaign).where(Campaign.id == campaign_id))
    row = res.scalar_one_or_none()
    if row is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Campaign not found")
    name = row.name
    await db.delete(row)
    await db.commit()
    await audit_log_service.record_admin_event(
        db,
        action=ACTION_CAMPAIGN_DELETED,
        admin_user_id=admin.id,
        admin_email=admin.email,
        details={"campaign_id": campaign_id, "name": name},
        request=request,
    )
    return None


# ---------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------


@router.get("/settings", response_model=TrackingSettingsOut)
async def get_settings_endpoint(
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> TrackingSettingsOut:
    row = await svc_get_settings(db)
    return settings_to_dto(row)


@router.put("/settings", response_model=TrackingSettingsOut)
async def update_settings_endpoint(
    payload: TrackingSettingsUpdate,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> TrackingSettingsOut:
    dto = await svc_update_settings(db, payload)
    await audit_log_service.record_admin_event(
        db,
        action=ACTION_SETTINGS_UPDATED,
        admin_user_id=admin.id,
        admin_email=admin.email,
        details=payload.model_dump(exclude_unset=True),
        request=request,
    )
    return dto