import hashlib
import json
from dataclasses import dataclass
from datetime import datetime, UTC

from bb_integrations_lib.models.pipeline_structs import  NoPipelineData
from bb_integrations_lib.protocols.pipelines import Step
from loguru import logger
from pymongo.asynchronous.database import AsyncDatabase


@dataclass
class FilterResult:
    changed_records: list[dict]
    hash_updates: list[dict]


class SamsaraFilterChangedStep(Step):
    """
    Filter ELD records to only include those with changed data.

    Uses MD5 hash comparison against stored hashes in MongoDB to avoid
    redundant updates to Gravitate.
    """


    def __init__(self, mongo_db: AsyncDatabase,
                 collection_name: str = "HosSync",
                 *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.mongo_db = mongo_db
        self.collection_name = collection_name

    def describe(self) -> str:
        return "Filter ELD records to changed only (hash comparison)"

    async def ensure_indexes(self):
        """Ensure required indexes exist on the collection."""
        collection = self.mongo_db[self.collection_name]
        await collection.create_index("driver_id", unique=True)

    def compute_hash(self, record: dict) -> str:
        """
        Compute MD5 hash of an ELD record.

        The hash is computed from the JSON-serialized record, excluding
        fields that change with each fetch but don't represent actual data changes.
        """
        exclude = ("source_id", "duty_period_end")
        hashable_record = {k: v for k, v in record.items() if k not in exclude}
        json_str = json.dumps(hashable_record, sort_keys=True, default=str)
        return hashlib.md5(json_str.encode()).hexdigest()


    def hash_all_eld_records(self, eld_records: list[dict]) -> dict:
        """Compute hashes for all records"""
        record_hashes = {}
        for record in eld_records:
            driver_id = record.get("driver_id")
            if driver_id:
                record_hashes[driver_id] = {
                    "record": record,
                    "hash": self.compute_hash(record),
                }
        return record_hashes

    def filter_changed_records(
        self,
        record_hashes: dict[str, dict],
        existing_hashes: dict[str, dict],
    ) -> tuple[list[dict], list[dict]]:
        """
        Filter records to only those with changed data by comparing hashes.

        Args:
            record_hashes: Dict of driver_id -> {"record": dict, "hash": str}
            existing_hashes: Dict of driver_id -> {"hash": str, "version": int}

        Returns:
            Tuple of (changed_records, hash_updates)
        """
        changed_records = []
        hash_updates = []

        for driver_id, data in record_hashes.items():
            existing = existing_hashes.get(driver_id)

            if existing is None:
                # New driver - first sync
                changed_records.append(data["record"])
                hash_updates.append({
                    "driver_id": driver_id,
                    "source_id": data["record"].get("source_id", ""),
                    "hash": data["hash"],
                    "version": 1,
                    "updated_at": datetime.now(UTC),
                })
            elif existing["hash"] != data["hash"]:
                # Hash changed - data updated
                changed_records.append(data["record"])
                hash_updates.append({
                    "driver_id": driver_id,
                    "source_id": data["record"].get("source_id", ""),
                    "hash": data["hash"],
                    "version": existing.get("version", 0) + 1,
                    "updated_at": datetime.now(UTC),
                })
            # else: hash matches, skip this driver

        return changed_records, hash_updates

    async def execute(self, eld_records: list[dict]) -> FilterResult:
        """
        Filter ELD records to only those with changed data.

        Args:
            eld_records: List of Gravitate ELD record dicts from transform step

        Returns:
            FilterResult with changed_records and hash_updates for upload step
        """
        if not eld_records:
            msg = "No ELD records to filter"
            logger.info(msg)
            raise NoPipelineData(msg)

        await self.ensure_indexes()
        record_hashes = self.hash_all_eld_records(eld_records)

        driver_ids = list(record_hashes.keys())
        collection = self.mongo_db[self.collection_name]
        existing_docs = await collection.find(
            {"driver_id": {"$in": driver_ids}}, {"driver_id": 1, "hash": 1, "version": 1}
        ).to_list(None)

        existing_hashes = {doc["driver_id"]: doc for doc in existing_docs}

        changed_records, hash_updates = self.filter_changed_records(
            record_hashes, existing_hashes
        )

        skipped_count = len(eld_records) - len(changed_records)
        logger.info(
            f"Filtered ELD records: {len(changed_records)} changed, {skipped_count} unchanged (skipped)"
        )

        return FilterResult(changed_records=changed_records, hash_updates=hash_updates)
