import re
from datetime import datetime, timedelta
from bb_integrations_lib.protocols.pipelines import Step
from bb_integrations_lib.provider.api.samsara.model import (
    GravitateELDRecord,
    HOSComplianceData,
    ShiftData,
)
from loguru import logger


class SamsaraTransformELDStep(Step):
    """Transform Samsara HOS data to Gravitate ELD format."""

    def __init__(
        self,
        *args,
        drive_max_hours: float = 11.0,
        on_duty_max_hours: float = 14.0,
        meters_to_miles: float = 0.000621371,
        ms_to_hours: float = 1 / (1000 * 60 * 60),
        **kwargs,
    ):
        super().__init__(*args, **kwargs)
        self.drive_max_hours = drive_max_hours
        self.on_duty_max_hours = on_duty_max_hours
        self.meters_to_miles = meters_to_miles
        self.ms_to_hours = ms_to_hours

    def describe(self) -> str:
        return "Transform Samsara data to Gravitate ELD format"

    async def execute(self, hos_data: list[dict]) -> list[dict]:
        """
        Transform Samsara HOS data into Gravitate ELD records.

        Args:
            hos_data: List of dicts from SamsaraRetrieveHOSDataStep

        Returns:
            List of Gravitate ELD record dicts ready for upload
        """
        eld_records = []

        for item in hos_data:
            try:
                record = self.transform_single(item)
                if record:
                    eld_records.append(record.model_dump(mode="json"))
            except Exception as e:
                samsara_id = item.get("samsara_id", "unknown")
                logger.warning(f"Failed to transform HOS data for driver {samsara_id}: {e}")
                continue

        logger.info(f"Transformed {len(eld_records)} ELD records")
        return eld_records

    def find_duty_period_start(
        self, clocks: dict, retrieve_timestamp: datetime
    ) -> datetime:
        """
        Calculate duty period start from Samsara shift clock.

        The shiftRemainingDurationMs represents time remaining in the 14-hour
        on-duty window since the shift began.

        Args:
            clocks: Samsara clocks dict with shift.shiftRemainingDurationMs
            retrieve_timestamp: Current retrieve timestamp

        Returns:
            The duty period start datetime (UTC)
        """
        shift_remaining_ms = clocks.get("shift", {}).get("shiftRemainingDurationMs", 0)
        shift_remaining_hours = shift_remaining_ms * self.ms_to_hours
        elapsed_shift_hours = self.on_duty_max_hours - shift_remaining_hours

        # Clamp to non-negative (in case of clock reset)
        elapsed_shift_hours = max(0, elapsed_shift_hours)

        return retrieve_timestamp - timedelta(hours=elapsed_shift_hours)

    def calculate_hours_from_clocks(
        self, clocks: dict
    ) -> tuple[float, float, float] | None:
        """
        Calculate drive, on-duty, and cycle remaining hours from Samsara clocks data.

        Args:
            clocks: Samsara clocks dict with drive, shift, and cycle keys

        Returns:
            Tuple of (total_drive_hours, total_on_duty_hours, cycle_remaining_hours)
            or None if clocks data is missing
        """
        if not clocks:
            return None

        drive_remaining_ms = clocks.get("drive", {}).get("driveRemainingDurationMs", 0)
        shift_remaining_ms = clocks.get("shift", {}).get("shiftRemainingDurationMs", 0)
        cycle_remaining_ms = clocks.get("cycle", {}).get("cycleRemainingDurationMs", 0)

        drive_remaining_hours = drive_remaining_ms * self.ms_to_hours
        shift_remaining_hours = shift_remaining_ms * self.ms_to_hours
        cycle_remaining_hours = cycle_remaining_ms * self.ms_to_hours

        total_drive_hours = self.drive_max_hours - drive_remaining_hours
        total_on_duty_hours = self.on_duty_max_hours - shift_remaining_hours

        return total_drive_hours, total_on_duty_hours, cycle_remaining_hours

    def parse_cycle_info_from_daily_log(
        self, daily_log: dict, cycle_remaining_hours: float
    ) -> tuple[float, float, str]:
        """
        Parse cycle information from daily log and calculate weekly on-duty hours.

        Args:
            daily_log: Samsara daily log dict
            cycle_remaining_hours: Remaining cycle hours from clocks

        Returns:
            Tuple of (weekly_max_hours, weekly_on_duty_hours, hos_cycle)
        """
        weekly_max_hours = 70.0  # Default to 70-hour
        hos_cycle = "8-day"  # Default

        driver_info = daily_log.get("driver", {})
        eld_settings = driver_info.get("eldSettings", {})
        rulesets = eld_settings.get("rulesets", [])

        if rulesets:
            cycle_str = rulesets[0].get("cycle", "")
            weekly_max_hours, hos_cycle = self.parse_cycle_string(cycle_str)

        weekly_on_duty_hours = weekly_max_hours - cycle_remaining_hours

        return weekly_max_hours, weekly_on_duty_hours, hos_cycle

    def transform_single(self, item: dict) -> GravitateELDRecord | None:
        """Transform a single driver's HOS data to Gravitate ELD format."""
        gravitate_driver = item["gravitate_driver"]
        samsara_id = item["samsara_id"]
        clocks = item["clocks"]
        daily_log = item["daily_log"]
        retrieve_timestamp = item["retrieve_timestamp"]

        hours_data = self.calculate_hours_from_clocks(clocks)
        if hours_data is None:
            logger.debug(f"Skipping driver {samsara_id}: no clocks data")
            return None

        total_drive_hours, total_on_duty_hours, cycle_remaining_hours = hours_data

        weekly_max_hours, weekly_on_duty_hours, hos_cycle = self.parse_cycle_info_from_daily_log(
            daily_log, cycle_remaining_hours
        )

        # Get distance traveled (convert meters to miles)
        distance_meters = daily_log.get("distanceTraveled", {}).get(
            "driveDistanceMeters", 0
        )
        total_miles = distance_meters * self.meters_to_miles

        # Calculate duty period start from shift clock
        duty_period_start = self.find_duty_period_start(clocks, retrieve_timestamp)
        duty_period_end = retrieve_timestamp  # Current time

        # Generate source_id with timestamp
        timestamp_str = retrieve_timestamp.strftime("%Y%m%dT%H%M%SZ")
        source_id = f"samsara_{samsara_id}_{timestamp_str}"

        # Get Gravitate driver ID
        gravitate_driver_id = gravitate_driver.get("_id") or gravitate_driver.get("id")

        return GravitateELDRecord(
            source_system="samsara",
            source_id=source_id,
            driver_id=gravitate_driver_id,
            duty_period_start=duty_period_start,
            duty_period_end=duty_period_end,
            shift_data=ShiftData(
                total_drive_hours=round(max(0, total_drive_hours), 2),
                total_on_duty_hours=round(max(0, total_on_duty_hours), 2),
                total_miles=round(total_miles, 2),
            ),
            hos_compliance_data=HOSComplianceData(
                drive_time_max_shift_hours=self.drive_max_hours,
                on_duty_max_shift_hours=self.on_duty_max_hours,
                weekly_on_duty_hours=round(max(0, weekly_on_duty_hours), 2),
                weekly_on_duty_max_hours=weekly_max_hours,
                hos_cycle=hos_cycle,
            ),
        )

    def parse_cycle_string(self, cycle_str: str) -> tuple[float, str]:
        """
        Parse Samsara cycle string like "USA 70 hour / 8 day" into (70.0, "8-day").

        Args:
            cycle_str: Samsara cycle string (e.g., "USA 70 hour / 8 day")

        Returns:
            Tuple of (max_hours, cycle_type) e.g., (70.0, "8-day")
        """
        # Default values
        max_hours = 70.0
        cycle_type = "8-day"

        # Try to extract hours (e.g., "70 hour" or "60 hour")
        hour_match = re.search(r"(\d+)\s*hour", cycle_str, re.IGNORECASE)
        if hour_match:
            max_hours = float(hour_match.group(1))

        # Try to extract days (e.g., "8 day" or "7 day")
        day_match = re.search(r"(\d+)\s*day", cycle_str, re.IGNORECASE)
        if day_match:
            cycle_type = f"{day_match.group(1)}-day"

        return max_hours, cycle_type
