from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from sqlalchemy import desc
from pathlib import Path
from datetime import datetime, timedelta, timezone
from typing import List, Dict, Any
import os
import logging
import httpx
from backend.database import get_db
from backend.models import UnitHistory, Emitter, RosterUnit
from backend.services.unit_location import get_active_location

log = logging.getLogger(__name__)

router = APIRouter(prefix="/api", tags=["activity"])

SFM_BASE_URL = os.getenv("SFM_BASE_URL", "http://localhost:8200")


def _humanize_age(seconds: float) -> str:
    if seconds < 60:
        return "just now"
    if seconds < 3600:
        return f"{int(seconds / 60)}m ago"
    if seconds < 86400:
        hrs = seconds / 3600
        return f"{int(hrs)}h {int((hrs % 1) * 60)}m ago"
    return f"{int(seconds / 86400)}d ago"

PHOTOS_BASE_DIR = Path("data/photos")


@router.get("/recent-activity")
def get_recent_activity(limit: int = 20, db: Session = Depends(get_db)):
    """
    Get recent activity feed combining unit history changes and photo uploads.
    Returns a unified timeline of events sorted by timestamp (newest first).
    """
    activities = []

    # Get recent history entries
    history_entries = db.query(UnitHistory)\
        .order_by(desc(UnitHistory.changed_at))\
        .limit(limit * 2)\
        .all()  # Get more than needed to mix with photos

    for entry in history_entries:
        activity = {
            "type": "history",
            "timestamp": entry.changed_at.isoformat(),
            "timestamp_unix": entry.changed_at.timestamp(),
            "unit_id": entry.unit_id,
            "change_type": entry.change_type,
            "field_name": entry.field_name,
            "old_value": entry.old_value,
            "new_value": entry.new_value,
            "source": entry.source,
            "notes": entry.notes
        }
        activities.append(activity)

    # Get recent photos
    if PHOTOS_BASE_DIR.exists():
        image_extensions = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
        photo_activities = []

        for unit_dir in PHOTOS_BASE_DIR.iterdir():
            if not unit_dir.is_dir():
                continue

            unit_id = unit_dir.name

            for file_path in unit_dir.iterdir():
                if file_path.is_file() and file_path.suffix.lower() in image_extensions:
                    modified_time = file_path.stat().st_mtime
                    photo_activities.append({
                        "type": "photo",
                        "timestamp": datetime.fromtimestamp(modified_time).isoformat(),
                        "timestamp_unix": modified_time,
                        "unit_id": unit_id,
                        "filename": file_path.name,
                        "photo_url": f"/api/unit/{unit_id}/photo/{file_path.name}"
                    })

        activities.extend(photo_activities)

    # Sort all activities by timestamp (newest first)
    activities.sort(key=lambda x: x["timestamp_unix"], reverse=True)

    # Limit to requested number
    activities = activities[:limit]

    return {
        "activities": activities,
        "total": len(activities)
    }


@router.get("/recent-callins")
def get_recent_callins(hours: int = 6, limit: int = None, db: Session = Depends(get_db)):
    """
    Get recent unit call-ins (units that have reported recently).
    Returns units sorted by most recent last_seen timestamp.

    Args:
        hours: Look back this many hours (default: 6)
        limit: Maximum number of results (default: None = all)
    """
    # Calculate the time threshold
    time_threshold = datetime.now(timezone.utc) - timedelta(hours=hours)

    # Query emitters with recent activity, joined with roster info
    recent_emitters = db.query(Emitter)\
        .filter(Emitter.last_seen >= time_threshold)\
        .order_by(desc(Emitter.last_seen))\
        .all()

    # Get roster info for all units
    roster_dict = {r.id: r for r in db.query(RosterUnit).all()}

    call_ins = []
    for emitter in recent_emitters:
        roster_unit = roster_dict.get(emitter.id)

        # Calculate time since last seen
        last_seen_utc = emitter.last_seen.replace(tzinfo=timezone.utc) if emitter.last_seen.tzinfo is None else emitter.last_seen
        time_diff = datetime.now(timezone.utc) - last_seen_utc

        # Format time ago
        if time_diff.total_seconds() < 60:
            time_ago = "just now"
        elif time_diff.total_seconds() < 3600:
            minutes = int(time_diff.total_seconds() / 60)
            time_ago = f"{minutes}m ago"
        else:
            hours_ago = time_diff.total_seconds() / 3600
            if hours_ago < 24:
                time_ago = f"{int(hours_ago)}h {int((hours_ago % 1) * 60)}m ago"
            else:
                days = int(hours_ago / 24)
                time_ago = f"{days}d ago"

        loc = get_active_location(db, emitter.id) if roster_unit else None
        call_in = {
            "unit_id": emitter.id,
            "last_seen": emitter.last_seen.isoformat(),
            "time_ago": time_ago,
            "status": emitter.status,
            "device_type": roster_unit.device_type if roster_unit else "seismograph",
            "deployed": roster_unit.deployed if roster_unit else False,
            "note": roster_unit.note if roster_unit and roster_unit.note else "",
            "location": (loc or {}).get("address") or (loc or {}).get("name") or ""
        }
        call_ins.append(call_in)

    # Apply limit if specified
    if limit:
        call_ins = call_ins[:limit]

    return {
        "call_ins": call_ins,
        "total": len(call_ins),
        "hours": hours,
        "time_threshold": time_threshold.isoformat()
    }


@router.get("/recent-event-callins")
async def get_recent_event_callins(limit: int = 10, db: Session = Depends(get_db)):
    """
    Recent unit call-ins derived from SFM event forwards.

    Architecture context: the live ACH replacement is on hold, so call-homes
    arrive as Blastware ACH event files forwarded by series3-watcher and
    landed in the SFM events store.  One event ≈ one call-in.  This is the
    forward-looking source of "recent call-ins" that will eventually replace
    the heartbeat-based /recent-callins endpoint entirely.

    Each row represents one event; multiple consecutive events from the same
    serial are intentionally NOT collapsed — each one is a distinct call-home.
    """
    try:
        async with httpx.AsyncClient(timeout=10.0) as client:
            resp = await client.get(
                f"{SFM_BASE_URL}/db/events",
                params={"limit": limit},
            )
            resp.raise_for_status()
            payload = resp.json()
    except httpx.HTTPError as e:
        log.warning("SFM /db/events failed for recent-event-callins: %s", e)
        return {"call_ins": [], "total": 0, "error": str(e)}

    events = payload.get("events", []) or []

    # Bulk-resolve serials → roster (single query, no N+1)
    serials = list({ev.get("serial") for ev in events if ev.get("serial")})
    roster_map: Dict[str, RosterUnit] = {}
    if serials:
        roster_map = {
            r.id: r
            for r in db.query(RosterUnit).filter(RosterUnit.id.in_(serials)).all()
        }

    now = datetime.now(timezone.utc)
    call_ins: List[Dict[str, Any]] = []

    for ev in events:
        serial = ev.get("serial")
        if not serial:
            continue

        roster = roster_map.get(serial)

        # created_at = when SFM received the forward.  Falls back to the event
        # timestamp if the SFM payload didn't carry created_at (older rows).
        created_at_str = ev.get("created_at") or ev.get("timestamp")
        time_ago = "—"
        if created_at_str:
            try:
                ts = datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
                if ts.tzinfo is None:
                    ts = ts.replace(tzinfo=timezone.utc)
                time_ago = _humanize_age((now - ts).total_seconds())
            except ValueError:
                pass

        call_ins.append({
            "unit_id":         serial,
            "serial":          serial,
            "event_id":        ev.get("id"),
            "event_timestamp": ev.get("timestamp"),
            "created_at":      ev.get("created_at"),
            "time_ago":        time_ago,
            "peak_vector_sum": ev.get("peak_vector_sum"),
            "false_trigger":   bool(ev.get("false_trigger")),
            "sensor_location": ev.get("sensor_location") or "",
            "project":         ev.get("project") or "",
            "device_type":     roster.device_type if roster else "seismograph",
            "in_roster":       roster is not None,
            "note":            (roster.note if roster else "") or "",
        })

    return {
        "call_ins": call_ins,
        "total":    len(call_ins),
        "source":   "sfm-events",
    }
