"""Device repository for database operations on device connections and readings."""
from datetime import UTC, datetime, timedelta
from typing import Optional

from bson import ObjectId
from pymongo import ReturnDocument, UpdateOne

from app.db import Database
from app.models.device_connection import DeviceConnection
from app.models.device_reading import DeviceReading

COL_CONNECTIONS = "device_connections"
COL_READINGS = "device_readings"


# ─── Connection CRUD ──────────────────────────────────────────────────

def create_connection(connection: DeviceConnection) -> DeviceConnection:
    """Create a new device connection."""
    now = datetime.now(UTC)
    to_insert = connection.model_copy(
        update={"created_at": now, "updated_at": now}
    ).to_bson()
    res = Database.get_db().get_collection(COL_CONNECTIONS).insert_one(to_insert)
    return DeviceConnection.from_bson({**to_insert, "_id": res.inserted_id})


def find_connection_by_id(connection_id: str) -> Optional[DeviceConnection]:
    """Find a device connection by its ID."""
    if not ObjectId.is_valid(connection_id):
        return None
    doc = Database.get_db().get_collection(COL_CONNECTIONS).find_one(
        {"_id": ObjectId(connection_id)}
    )
    return DeviceConnection.from_bson(doc) if doc else None


def find_connections_by_user(user_id: str) -> list[DeviceConnection]:
    """Find all device connections for a user."""
    if not ObjectId.is_valid(user_id):
        return []
    docs = Database.get_db().get_collection(COL_CONNECTIONS).find(
        {"userId": ObjectId(user_id)}
    )
    return [DeviceConnection.from_bson(doc) for doc in docs]


def find_connection_by_provider(user_id: str, provider: str) -> Optional[DeviceConnection]:
    """Find a specific provider connection for a user."""
    if not ObjectId.is_valid(user_id):
        return None
    doc = Database.get_db().get_collection(COL_CONNECTIONS).find_one(
        {"userId": ObjectId(user_id), "provider": provider}
    )
    return DeviceConnection.from_bson(doc) if doc else None


def update_connection(connection_id: str, updates: dict) -> Optional[DeviceConnection]:
    """Update a device connection."""
    if not ObjectId.is_valid(connection_id):
        return None
    db = Database.get_db()
    existing = db.get_collection(COL_CONNECTIONS).find_one(
        {"_id": ObjectId(connection_id)}
    )
    if not existing:
        return None

    merged = {**existing, **updates}
    candidate = DeviceConnection.from_bson(merged).model_copy(
        update={"updated_at": datetime.now(UTC)}
    )
    to_set = candidate.to_bson()
    to_set.pop("_id", None)

    doc = db.get_collection(COL_CONNECTIONS).find_one_and_update(
        {"_id": ObjectId(connection_id)},
        {"$set": to_set},
        return_document=ReturnDocument.AFTER,
    )
    return DeviceConnection.from_bson(doc) if doc else None


def delete_connection(connection_id: str) -> bool:
    """Delete a device connection. Returns True if deleted."""
    if not ObjectId.is_valid(connection_id):
        return False
    result = Database.get_db().get_collection(COL_CONNECTIONS).delete_one(
        {"_id": ObjectId(connection_id)}
    )
    return result.deleted_count > 0


# ─── Reading operations ───────────────────────────────────────────────

def upsert_reading(reading: DeviceReading) -> DeviceReading:
    """Insert or update a reading based on userId+provider+metric+date for daily summaries."""
    now = datetime.now(UTC)
    doc = reading.model_copy(update={"updated_at": now}).to_bson()
    doc.pop("_id", None)

    filter_key = {
        "userId": reading.userId,
        "provider": reading.provider,
        "metric": reading.metric,
        "date": reading.date,
    }

    result = Database.get_db().get_collection(COL_READINGS).find_one_and_update(
        filter_key,
        {"$set": doc, "$setOnInsert": {"created_at": now}},
        upsert=True,
        return_document=ReturnDocument.AFTER,
    )
    return DeviceReading.from_bson(result)


def bulk_upsert_readings(readings: list[DeviceReading]) -> int:
    """Bulk upsert readings. Returns count of modified/upserted documents."""
    if not readings:
        return 0

    now = datetime.now(UTC)
    operations = []
    for reading in readings:
        doc = reading.model_copy(update={"updated_at": now}).to_bson()
        doc.pop("_id", None)

        filter_key = {
            "userId": reading.userId,
            "provider": reading.provider,
            "metric": reading.metric,
            "date": reading.date,
        }
        operations.append(
            UpdateOne(filter_key, {"$set": doc, "$setOnInsert": {"created_at": now}}, upsert=True)
        )

    result = Database.get_db().get_collection(COL_READINGS).bulk_write(
        operations, ordered=False
    )
    return result.upserted_count + result.modified_count


def get_readings(
    user_id: str,
    metric: str,
    start_date: str,
    end_date: str,
    provider: Optional[str] = None,
) -> list[DeviceReading]:
    """Get readings for a user filtered by metric and date range."""
    if not ObjectId.is_valid(user_id):
        return []
    query: dict = {
        "userId": ObjectId(user_id),
        "metric": metric,
        "date": {"$gte": start_date, "$lte": end_date},
    }
    if provider:
        query["provider"] = provider

    docs = Database.get_db().get_collection(COL_READINGS).find(query).sort("date", 1)
    return [DeviceReading.from_bson(doc) for doc in docs]


def get_latest_reading(user_id: str, metric: str) -> Optional[DeviceReading]:
    """Get the most recent reading for a user and metric."""
    if not ObjectId.is_valid(user_id):
        return None
    doc = Database.get_db().get_collection(COL_READINGS).find_one(
        {"userId": ObjectId(user_id), "metric": metric},
        sort=[("date", -1)],
    )
    return DeviceReading.from_bson(doc) if doc else None


def get_daily_summary(user_id: str, date: str) -> dict[str, DeviceReading]:
    """Get all metric readings for a user on a given date, keyed by metric name."""
    if not ObjectId.is_valid(user_id):
        return {}
    docs = Database.get_db().get_collection(COL_READINGS).find(
        {"userId": ObjectId(user_id), "date": date, "metricType": "daily_summary"}
    )
    return {doc["metric"]: DeviceReading.from_bson(doc) for doc in docs}


def delete_readings_for_user(user_id: str, metrics: Optional[list[str]] = None) -> int:
    """Delete reading rows for a user. If `metrics` is provided, only those
    metric names are deleted; otherwise wipes everything for the user.
    Returns the deletion count.
    """
    if not ObjectId.is_valid(user_id):
        return 0
    query: dict = {"userId": ObjectId(user_id)}
    if metrics:
        query["metric"] = {"$in": metrics}
    result = Database.get_db().get_collection(COL_READINGS).delete_many(query)
    return result.deleted_count


def get_metrics_range(
    user_id: str,
    metrics: list[str],
    days: int = 7,
) -> dict[str, list[DeviceReading]]:
    """Bulk fetch multiple metrics for the last N days. Returns {metric: [readings]}."""
    if not ObjectId.is_valid(user_id):
        return {}
    today = datetime.now(UTC).strftime("%Y-%m-%d")
    start = (datetime.now(UTC) - timedelta(days=days)).strftime("%Y-%m-%d")

    docs = Database.get_db().get_collection(COL_READINGS).find(
        {
            "userId": ObjectId(user_id),
            "metric": {"$in": metrics},
            "date": {"$gte": start, "$lte": today},
        }
    ).sort("date", 1)

    result: dict[str, list[DeviceReading]] = {m: [] for m in metrics}
    for doc in docs:
        reading = DeviceReading.from_bson(doc)
        if reading.metric in result:
            result[reading.metric].append(reading)
    return result


# ─── Index management ─────────────────────────────────────────────────

def ensure_indexes():
    """Create required MongoDB indexes for device collections."""
    db = Database.get_db()

    # Compound unique index: prevents duplicate daily summaries from re-syncs
    db.get_collection(COL_READINGS).create_index(
        [("userId", 1), ("provider", 1), ("metric", 1), ("date", 1)],
        unique=True,
        name="uq_user_provider_metric_date",
    )

    # Query index: fast lookups for user metric history
    db.get_collection(COL_READINGS).create_index(
        [("userId", 1), ("metric", 1), ("date", 1)],
        name="idx_user_metric_date",
    )

    # Connection uniqueness: one connection per provider per user
    db.get_collection(COL_CONNECTIONS).create_index(
        [("userId", 1), ("provider", 1)],
        unique=True,
        name="uq_user_provider",
    )
