"""⛔ DADO DE BAJA el 2026-07-29.

La persistencia y las consultas se conservan temporalmente como registro
histórico, pero ninguna ruta activa importa este módulo. Acta canónica:
ITG_SCAPP/SW_SCAPP/docs/to-do/baja-live-summary.md.
"""

import copy
from collections import defaultdict
from datetime import datetime, timedelta, timezone

from pymongo import ASCENDING, DeleteMany, InsertOne, MongoClient
from pymongo.errors import BulkWriteError, CollectionInvalid

from Analytic.centro_ids import build_centro_id, capitalize_token


SCAPP_DATABASE_NAME = "ScappDB"
LIVE_SUMMARY_COLLECTION_NAME = "live_summary_ts"
SUPPORTED_SCHEMA_VERSIONS = {1}


def validate_and_normalize_live_summary_payload(payload):
    if not isinstance(payload, dict):
        raise ValueError("Payload must be a JSON object")

    normalized = copy.deepcopy(payload)
    required_fields = [
        "schema_version",
        "timestamp",
        "window_start",
        "window_end",
        "window_seconds",
        "center_id",
        "cages",
    ]
    for field in required_fields:
        if field not in normalized:
            raise ValueError(f"Missing field: {field}")

    try:
        normalized["schema_version"] = int(normalized["schema_version"])
    except (TypeError, ValueError):
        raise ValueError("Invalid field: schema_version")

    if normalized["schema_version"] not in SUPPORTED_SCHEMA_VERSIONS:
        raise ValueError("Unsupported schema_version")

    normalized["timestamp"] = _parse_datetime(normalized["timestamp"], "timestamp")
    normalized["window_start"] = _parse_datetime(normalized["window_start"], "window_start")
    normalized["window_end"] = _parse_datetime(normalized["window_end"], "window_end")

    if not isinstance(normalized["cages"], list):
        raise ValueError("Field 'cages' must be an array")

    if not isinstance(normalized["center_id"], dict):
        raise ValueError("Field 'center_id' must be an object")

    normalized["sources"] = normalized.get("sources", {})
    if normalized["sources"] is None:
        normalized["sources"] = {}
    if not isinstance(normalized["sources"], dict):
        raise ValueError("Field 'sources' must be an object")

    normalized["center_feeding"] = normalized.get("center_feeding", {})
    if normalized["center_feeding"] is None:
        normalized["center_feeding"] = {}
    if not isinstance(normalized["center_feeding"], dict):
        raise ValueError("Field 'center_feeding' must be an object")

    feeding_api = normalized["sources"].get("feeding_api")
    if isinstance(feeding_api, dict) and feeding_api.get("timestamp") is not None:
        feeding_api["timestamp"] = _parse_datetime(
            feeding_api["timestamp"], "sources.feeding_api.timestamp"
        )

    if normalized["center_feeding"].get("timestamp") is not None:
        normalized["center_feeding"]["timestamp"] = _parse_datetime(
            normalized["center_feeding"]["timestamp"], "center_feeding.timestamp"
        )

    for index, cage in enumerate(normalized["cages"]):
        if not isinstance(cage, dict):
            raise ValueError(f"Invalid cage entry at index {index}")

        for field in ("cage_id", "detection", "feeding", "sensors", "winch"):
            if field not in cage:
                raise ValueError(f"Missing field: cages[{index}].{field}")

        for block_name in ("detection", "feeding", "sensors", "winch"):
            block = cage.get(block_name)
            if not isinstance(block, dict):
                raise ValueError(f"Field 'cages[{index}].{block_name}' must be an object")
            if block.get("timestamp") is not None:
                block["timestamp"] = _parse_datetime(
                    block["timestamp"], f"cages[{index}].{block_name}.timestamp"
                )

        camera_ids = cage.get("camera_ids", [])
        if camera_ids is None:
            cage["camera_ids"] = []
        elif not isinstance(camera_ids, list):
            raise ValueError(f"Field 'cages[{index}].camera_ids' must be an array")

    return normalized


def ensure_live_summary_indexes(create_collection_if_missing: bool = False):
    client = MongoClient("mongodb://localhost:27017/")
    try:
        db = client[SCAPP_DATABASE_NAME]
        collection = get_live_summary_collection(
            db,
            create_if_missing=create_collection_if_missing,
        )
        if collection is not None:
            _ensure_live_summary_indexes(collection)
    finally:
        client.close()


def get_live_summary_collection(db, create_if_missing: bool = True):
    if LIVE_SUMMARY_COLLECTION_NAME not in db.list_collection_names():
        if not create_if_missing:
            return None
        try:
            db.create_collection(
                LIVE_SUMMARY_COLLECTION_NAME,
                timeseries={
                    "timeField": "timestamp",
                    "metaField": "metadata",
                    "granularity": "minutes",
                },
            )
        except CollectionInvalid:
            pass

    collection = db[LIVE_SUMMARY_COLLECTION_NAME]
    _ensure_live_summary_indexes(collection)
    return collection


def save_live_summary_payload(db, payload, region: str, area: str, centro: str):
    collection = get_live_summary_collection(db, create_if_missing=True)
    centro_id = build_centro_id(region, area, centro)
    received_at = datetime.utcnow()
    docs = _build_live_summary_docs(payload, centro_id, region, area, centro, received_at)

    delete_ops = []
    insert_ops = []
    for doc in docs:
        delete_ops.append(
            DeleteMany(
                {
                    "metadata.centro_id": doc["metadata"]["centro_id"],
                    "metadata.cage_id": doc["metadata"]["cage_id"],
                    "timestamp": doc["timestamp"],
                }
            )
        )
        insert_ops.append(InsertOne(doc))

    if delete_ops:
        try:
            collection.bulk_write(delete_ops, ordered=False)
        except BulkWriteError:
            pass

    if insert_ops:
        collection.bulk_write(insert_ops, ordered=False)

    return {
        "centro_id": centro_id,
        "documents_inserted": len(insert_ops),
        "received_at": received_at,
    }


def get_last_live_summary_received_at(db, centro_id: str):
    collection = get_live_summary_collection(db, create_if_missing=False)
    if collection is None:
        return None

    latest_doc = collection.find_one(
        {
            "metadata.centro_id": centro_id,
            "received_at": {"$exists": True},
        },
        projection={"received_at": 1, "_id": 0},
        sort=[("received_at", -1)],
    )
    if not latest_doc:
        return None
    return latest_doc.get("received_at")


def get_live_summary_center_data(db, centro_id: str, start_day: datetime, day_ranges: int):
    docs = _fetch_docs(
        db,
        {"metadata.centro_id": centro_id},
        start_day,
        day_ranges,
    )
    return _reconstruct_center_docs(docs)


def get_live_summary_area_data(db, area_id: str, start_day: datetime, day_ranges: int):
    region, area = area_id.split("-")
    docs = _fetch_docs(
        db,
        {
            "metadata.region": capitalize_token(region),
            "metadata.area": capitalize_token(area),
        },
        start_day,
        day_ranges,
    )
    grouped_by_center = defaultdict(list)
    for doc in docs:
        grouped_by_center[doc["metadata"]["centro_id"]].append(doc)

    response = {}
    for centro_id, center_docs in grouped_by_center.items():
        center_day_map = _reconstruct_center_docs(center_docs)
        for day, payloads in center_day_map.items():
            response.setdefault(day, {})[centro_id] = payloads

    return dict(sorted(response.items()))


def get_live_summary_region_data(db, region: str, start_day: datetime, day_ranges: int):
    docs = _fetch_docs(
        db,
        {"metadata.region": capitalize_token(region)},
        start_day,
        day_ranges,
    )
    grouped_docs = defaultdict(lambda: defaultdict(list))
    for doc in docs:
        grouped_docs[doc["metadata"]["area"]][doc["metadata"]["centro_id"]].append(doc)

    response = {}
    for area_name, centers in grouped_docs.items():
        area_id = f"{capitalize_token(region)}-{area_name}"
        for centro_id, center_docs in centers.items():
            center_day_map = _reconstruct_center_docs(center_docs)
            for day, payloads in center_day_map.items():
                response.setdefault(day, {}).setdefault(area_id, {})[centro_id] = payloads

    return dict(sorted(response.items()))


def _fetch_docs(db, match_filter, start_day: datetime, day_ranges: int):
    collection = get_live_summary_collection(db, create_if_missing=False)
    if collection is None:
        return []

    end_day = start_day + timedelta(days=int(day_ranges) + 1)
    query = {
        **match_filter,
        "timestamp": {
            "$gte": start_day,
            "$lt": end_day,
        },
    }
    cursor = collection.find(query, projection={"_id": 0}).sort(
        [
            ("timestamp", ASCENDING),
            ("metadata.centro_id", ASCENDING),
            ("metadata.cage_id", ASCENDING),
        ]
    )
    return list(cursor)


def _reconstruct_center_docs(docs):
    grouped_by_day = defaultdict(dict)

    for doc in docs:
        timestamp = doc["timestamp"]
        day_key = timestamp.strftime("%Y-%m-%d")
        timestamp_key = timestamp.isoformat()

        payload = grouped_by_day[day_key].get(timestamp_key)
        if payload is None:
            payload = {
                "_sort_ts": timestamp,
                "schema_version": doc.get("schema_version"),
                "timestamp": _serialize_datetime(timestamp),
                "window_start": _serialize_datetime(doc.get("window_start")),
                "window_end": _serialize_datetime(doc.get("window_end")),
                "window_seconds": doc.get("window_seconds"),
                "center_id": _json_safe(doc.get("center_id", {})),
                "sources": _json_safe(doc.get("sources", {})),
                "center_feeding": _json_safe(doc.get("center_feeding", {})),
                "cages": [],
            }
            grouped_by_day[day_key][timestamp_key] = payload

        payload["cages"].append(
            {
                "cage_id": doc.get("metadata", {}).get("cage_id"),
                "cage_name": doc.get("metadata", {}).get("cage_name"),
                "canonical_camera_id": doc.get("metadata", {}).get("canonical_camera_id"),
                "camera_ids": _json_safe(doc.get("camera_ids", [])),
                "detection": _json_safe(doc.get("detection", {})),
                "feeding": _json_safe(doc.get("feeding", {})),
                "sensors": _json_safe(doc.get("sensors", {})),
                "winch": _json_safe(doc.get("winch", {})),
            }
        )

    response = {}
    for day_key, payloads_by_ts in grouped_by_day.items():
        ordered_payloads = sorted(
            payloads_by_ts.values(),
            key=lambda item: item["_sort_ts"],
        )
        for payload in ordered_payloads:
            payload["cages"].sort(key=lambda cage: str(cage.get("cage_id", "")))
            payload.pop("_sort_ts", None)
        response[day_key] = ordered_payloads

    return dict(sorted(response.items()))


def _build_live_summary_docs(payload, centro_id, region, area, centro, received_at):
    docs = []
    base_metadata = {
        "centro_id": centro_id,
        "region": capitalize_token(region),
        "area": capitalize_token(area),
        "centro": capitalize_token(centro),
    }

    for cage in payload["cages"]:
        metadata = {
            **base_metadata,
            "cage_id": cage.get("cage_id"),
            "cage_name": cage.get("cage_name"),
            "canonical_camera_id": cage.get("canonical_camera_id"),
        }
        docs.append(
            {
                "schema_version": payload["schema_version"],
                "timestamp": payload["timestamp"],
                "window_start": payload["window_start"],
                "window_end": payload["window_end"],
                "window_seconds": payload["window_seconds"],
                "metadata": metadata,
                "center_id": copy.deepcopy(payload.get("center_id", {})),
                "sources": copy.deepcopy(payload.get("sources", {})),
                "center_feeding": copy.deepcopy(payload.get("center_feeding", {})),
                "camera_ids": copy.deepcopy(cage.get("camera_ids", [])),
                "detection": copy.deepcopy(cage.get("detection", {})),
                "feeding": copy.deepcopy(cage.get("feeding", {})),
                "sensors": copy.deepcopy(cage.get("sensors", {})),
                "winch": copy.deepcopy(cage.get("winch", {})),
                "received_at": received_at,
            }
        )
    return docs


def _ensure_live_summary_indexes(collection):
    desired_indexes = {
        "idx_live_summary_centro_timestamp": [
            ("metadata.centro_id", ASCENDING),
            ("timestamp", ASCENDING),
        ],
        "idx_live_summary_centro_cage_timestamp": [
            ("metadata.centro_id", ASCENDING),
            ("metadata.cage_id", ASCENDING),
            ("timestamp", ASCENDING),
        ],
        "idx_live_summary_region_area_timestamp": [
            ("metadata.region", ASCENDING),
            ("metadata.area", ASCENDING),
            ("timestamp", ASCENDING),
        ],
        "idx_live_summary_region_timestamp": [
            ("metadata.region", ASCENDING),
            ("timestamp", ASCENDING),
        ],
        "idx_live_summary_centro_received_at": [
            ("metadata.centro_id", ASCENDING),
            ("received_at", ASCENDING),
        ],
    }

    current_indexes = collection.index_information()
    for index_name, index_spec in desired_indexes.items():
        if index_name not in current_indexes:
            collection.create_index(index_spec, name=index_name, background=True)


def _parse_datetime(value, field_name: str) -> datetime:
    if isinstance(value, datetime):
        dt_value = value
    elif isinstance(value, str):
        raw_value = value.strip()
        if raw_value.endswith("Z"):
            raw_value = raw_value[:-1] + "+00:00"
        try:
            dt_value = datetime.fromisoformat(raw_value)
        except ValueError:
            try:
                dt_value = datetime.strptime(raw_value, "%Y-%m-%d %H:%M:%S")
            except ValueError as exc:
                raise ValueError(f"Invalid datetime for {field_name}") from exc
    else:
        raise ValueError(f"Invalid datetime for {field_name}")

    if dt_value.tzinfo is not None:
        dt_value = dt_value.astimezone(timezone.utc).replace(tzinfo=None)

    return dt_value


def _serialize_datetime(value):
    if value is None:
        return None
    if not isinstance(value, datetime):
        return value
    return f"{value.isoformat()}Z"


def _json_safe(value):
    if isinstance(value, datetime):
        return _serialize_datetime(value)
    if isinstance(value, list):
        return [_json_safe(item) for item in value]
    if isinstance(value, dict):
        return {key: _json_safe(item) for key, item in value.items()}
    return value
