"""Track tenant login sessions for platform operator monitoring."""
from __future__ import annotations

import logging
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

from fastapi import Request
from sqlalchemy.orm import Session

from app.models.activity_log import UserSession
from app.models.branch import Branch
from app.models.company import Company

logger = logging.getLogger(__name__)

DEFAULT_STALE_MINUTES = 45


def client_ip(request: Optional[Request]) -> Optional[str]:
    if not request:
        return None
    forwarded = request.headers.get("x-forwarded-for") or request.headers.get("X-Forwarded-For")
    if forwarded:
        return forwarded.split(",")[0].strip()
    if request.client:
        return request.client.host
    return None


def user_agent_hint(ua: Optional[str]) -> Optional[str]:
    if not ua:
        return None
    ua = ua.strip()
    if len(ua) <= 72:
        return ua
    return ua[:69] + "…"


def _browser_label(ua: Optional[str]) -> Optional[str]:
    if not ua:
        return None
    u = ua.lower()
    if "edg/" in u or "edge/" in u:
        return "Edge"
    if "chrome/" in u and "chromium" not in u:
        return "Chrome"
    if "firefox/" in u:
        return "Firefox"
    if "safari/" in u and "chrome" not in u:
        return "Safari"
    if "curl/" in u:
        return "curl"
    return "Browser"


class SessionTrackingService:
    def __init__(self, db: Session):
        self.db = db

    def start_session(
        self,
        *,
        jti: str,
        user_id: str,
        username: str,
        role: Optional[str] = None,
        company: Optional[Company] = None,
        login_branch: Optional[Branch] = None,
        request: Optional[Request] = None,
        login_method: str = "password",
        cnp_support: bool = False,
    ) -> Optional[UserSession]:
        if not jti:
            return None
        now = datetime.utcnow()
        meta: Dict[str, Any] = {
            "role": role,
            "company_id": str(company.id) if company else None,
            "company_code": company.company_code if company else None,
            "company_name": company.name if company else None,
            "branch_code": login_branch.code if login_branch else None,
            "branch_name": login_branch.name if login_branch else None,
            "cnp_support": bool(cnp_support),
            "client_hint": _browser_label(request.headers.get("user-agent") if request else None),
        }
        row = UserSession(
            user_id=str(user_id),
            username=username,
            session_token=jti,
            session_start=now,
            is_active=True,
            ip_address=client_ip(request),
            user_agent=(request.headers.get("user-agent") if request else None),
            login_method=login_method,
            last_activity=now,
            branch_id=str(login_branch.id) if login_branch else None,
            extra_data=meta,
        )
        self.db.add(row)
        self.db.commit()
        self.db.refresh(row)
        return row

    def touch_session(self, jti: Optional[str]) -> bool:
        if not jti:
            return False
        row = (
            self.db.query(UserSession)
            .filter(UserSession.session_token == jti, UserSession.is_active == True)
            .first()
        )
        if not row:
            return False
        row.last_activity = datetime.utcnow()
        try:
            count = int(row.activity_count or "0")
        except (TypeError, ValueError):
            count = 0
        row.activity_count = str(count + 1)
        self.db.commit()
        return True

    def end_session(self, jti: Optional[str], reason: str = "logout") -> bool:
        if not jti:
            return False
        row = (
            self.db.query(UserSession)
            .filter(UserSession.session_token == jti, UserSession.is_active == True)
            .first()
        )
        if not row:
            return False
        now = datetime.utcnow()
        row.is_active = False
        row.session_end = now
        row.logout_reason = reason
        row.last_activity = now
        self.db.commit()
        return True

    def count_active(self, stale_minutes: int = DEFAULT_STALE_MINUTES) -> int:
        cutoff = datetime.utcnow() - timedelta(minutes=stale_minutes)
        try:
            return (
                self.db.query(UserSession)
                .filter(UserSession.is_active == True, UserSession.last_activity >= cutoff)
                .count()
            )
        except Exception as exc:
            logger.debug("count_active sessions failed: %s", exc)
            return 0

    def list_active_for_platform(
        self,
        *,
        company_id: Optional[str] = None,
        search: Optional[str] = None,
        stale_minutes: int = DEFAULT_STALE_MINUTES,
        limit: int = 500,
    ) -> List[Dict[str, Any]]:
        cutoff = datetime.utcnow() - timedelta(minutes=stale_minutes)
        try:
            q = (
                self.db.query(UserSession)
                .filter(UserSession.is_active == True, UserSession.last_activity >= cutoff)
                .order_by(UserSession.last_activity.desc())
            )
            rows = q.limit(limit).all()
        except Exception as exc:
            logger.warning("list_active_for_platform failed: %s", exc)
            return []

        out: List[Dict[str, Any]] = []
        now = datetime.utcnow()
        needle = (search or "").strip().lower()
        for s in rows:
            meta = s.extra_data if isinstance(s.extra_data, dict) else {}
            cid = meta.get("company_id")
            if company_id and cid != company_id:
                continue
            if needle:
                blob = " ".join(
                    filter(
                        None,
                        [
                            s.username,
                            meta.get("company_code"),
                            meta.get("company_name"),
                            meta.get("branch_code"),
                            meta.get("branch_name"),
                            s.ip_address,
                        ],
                    )
                ).lower()
                if needle not in blob:
                    continue
            idle = (now - (s.last_activity or s.session_start)).total_seconds() / 60.0
            out.append(
                {
                    "id": s.id,
                    "user_id": s.user_id,
                    "username": s.username,
                    "role": meta.get("role"),
                    "company_id": cid,
                    "company_code": meta.get("company_code"),
                    "company_name": meta.get("company_name"),
                    "branch_id": s.branch_id,
                    "branch_code": meta.get("branch_code"),
                    "branch_name": meta.get("branch_name"),
                    "ip_address": s.ip_address,
                    "user_agent": user_agent_hint(s.user_agent),
                    "client_hint": meta.get("client_hint") or _browser_label(s.user_agent),
                    "login_method": s.login_method,
                    "session_start": s.session_start,
                    "last_activity": s.last_activity,
                    "minutes_idle": round(idle, 1),
                    "cnp_support": bool(meta.get("cnp_support")),
                }
            )
        return out
