"""Native PiSugar I2C driver - direct hardware communication.

Bypasses pisugar-server daemon entirely for minimal CPU usage.
Supports:
- PiSugar 2 (4-LEDs) - IP5209 chip at I2C address 0x75
- PiSugar 2 Pro - IP5209 chip at I2C address 0x75
- PiSugar 3 / 3 Plus - IP5312 chip at I2C address 0x57

Features:
- Battery voltage, current, and percentage reading
- Power plug/charging detection
- Button tap detection (single, double, long press)
- RTC alarm for scheduled wake-up
- Force shutdown capability

Register information extracted from:
https://github.com/PiSugar/pisugar-power-manager-rs
"""
import asyncio
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Callable, List, Optional, Tuple

try:
    from smbus2 import SMBus
except ImportError:
    SMBus = None

from .base import UPSDriver, UPSData


class PiSugarModel(Enum):
    """Supported PiSugar models."""
    PISUGAR_2 = "PiSugar 2"
    PISUGAR_2_PRO = "PiSugar 2 Pro"
    PISUGAR_3 = "PiSugar 3"
    UNKNOWN = "Unknown PiSugar"


class ButtonEvent(Enum):
    """Button event types."""
    SINGLE_TAP = "single"
    DOUBLE_TAP = "double"
    LONG_PRESS = "long"


@dataclass
class ChipConfig:
    """Configuration for a specific battery management chip."""
    i2c_address: int
    voltage_reg_low: int
    voltage_reg_high: int
    current_reg_low: int
    current_reg_high: int
    power_status_reg: int
    power_plugged_mask: int
    model: PiSugarModel


# IP5209 chip used in PiSugar 2 series
IP5209_CONFIG = ChipConfig(
    i2c_address=0x75,
    voltage_reg_low=0xA2,
    voltage_reg_high=0xA3,
    current_reg_low=0xA4,
    current_reg_high=0xA5,
    power_status_reg=0x55,
    power_plugged_mask=0x10,  # Bit 4
    model=PiSugarModel.PISUGAR_2,
)

# IP5312 chip used in PiSugar 3 series
IP5312_CONFIG = ChipConfig(
    i2c_address=0x57,
    voltage_reg_low=0xD0,
    voltage_reg_high=0xD1,
    current_reg_low=0xD2,
    current_reg_high=0xD3,
    power_status_reg=0xDD,
    power_plugged_mask=0x1F,  # Value 0x1F = plugged in
    model=PiSugarModel.PISUGAR_3,
)

# Battery voltage to percentage lookup curve (voltage in mV -> percentage)
# Based on typical LiPo discharge curve
BATTERY_CURVE = [
    (4160, 100),
    (4050, 90),
    (3920, 80),
    (3800, 70),
    (3720, 60),
    (3650, 50),
    (3580, 40),
    (3520, 30),
    (3420, 20),
    (3300, 10),
    (3100, 0),
]


class PiSugarI2CDriver(UPSDriver):
    """Native I2C driver for PiSugar UPS devices.

    Reads directly from the IP5209/IP5312 battery management chip,
    eliminating the need for the pisugar-server daemon.
    """

    # Known I2C addresses for auto-detection
    KNOWN_ADDRESSES = {
        0x75: IP5209_CONFIG,  # PiSugar 2 series
        0x57: IP5312_CONFIG,  # PiSugar 3 series
    }

    # RTC address (SD3078) - used for detection but not read
    RTC_ADDRESS = 0x32

    @classmethod
    async def detect(cls, i2c_bus: int = 1, retries: int = 3, retry_delay: float = 0.5) -> Tuple[bool, Optional["PiSugarI2CDriver"]]:
        """Detect PiSugar hardware by probing I2C addresses.

        Args:
            i2c_bus: I2C bus number (default: 1 for Raspberry Pi)
            retries: Number of detection attempts (for early-boot scenarios)
            retry_delay: Delay between retries in seconds

        Returns:
            Tuple of (detected: bool, driver_instance or None)
        """
        if SMBus is None:
            print("PiSugar I2C: smbus2 not available")
            return (False, None)

        for attempt in range(retries):
            try:
                bus = SMBus(i2c_bus)
                try:
                    # Try each known address
                    for addr, config in cls.KNOWN_ADDRESSES.items():
                        try:
                            # Try to read a byte from the address
                            bus.read_byte(addr)

                            # Validate by reading voltage registers
                            try:
                                low = bus.read_byte_data(addr, config.voltage_reg_low)
                                high = bus.read_byte_data(addr, config.voltage_reg_high)

                                # Success - create driver instance
                                print(f"PiSugar I2C: Found {config.model.value} at 0x{addr:02X} (voltage regs: {low}, {high})")
                                driver = cls(chip_config=config, i2c_bus=i2c_bus)
                                return (True, driver)
                            except OSError as e:
                                # Address responded but not the expected chip
                                print(f"PiSugar I2C: 0x{addr:02X} responded but voltage reg read failed: {e}")
                                continue

                        except OSError:
                            # No device at this address
                            continue

                finally:
                    bus.close()

            except Exception as e:
                if attempt < retries - 1:
                    print(f"PiSugar I2C: Detection attempt {attempt + 1} failed ({e}), retrying...")
                    await asyncio.sleep(retry_delay)
                else:
                    print(f"PiSugar I2C: All detection attempts failed: {e}")

        return (False, None)

    def __init__(
        self,
        chip_config: ChipConfig = IP5209_CONFIG,
        i2c_bus: int = 1
    ):
        """Initialize PiSugar I2C driver.

        Args:
            chip_config: Configuration for the specific chip
            i2c_bus: I2C bus number (default: 1)
        """
        self._config = chip_config
        self._i2c_bus = i2c_bus
        self._bus: Optional[SMBus] = None
        self._available = False

    async def initialize(self) -> bool:
        """Initialize I2C connection.

        Returns:
            True if initialization successful
        """
        if SMBus is None:
            print("smbus2 library not available for PiSugar I2C driver")
            return False

        try:
            self._bus = SMBus(self._i2c_bus)

            # Verify we can read from the chip
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(
                None,
                self._bus.read_byte_data,
                self._config.i2c_address,
                self._config.voltage_reg_low
            )

            self._available = True
            return True

        except Exception as e:
            print(f"Failed to initialize PiSugar I2C: {e}")
            self._available = False
            if self._bus:
                self._bus.close()
                self._bus = None
            return False

    async def read_data(self) -> UPSData:
        """Read battery data directly from I2C.

        Returns:
            UPSData with current readings
        """
        if not self._bus or not self._available:
            raise RuntimeError("PiSugar I2C not initialized")

        loop = asyncio.get_event_loop()

        # Read voltage
        voltage = await self._read_voltage(loop)

        # Read current
        current = await self._read_current(loop)

        # Read power status
        is_charging = await self._read_power_status(loop)

        # Convert voltage to percentage using lookup curve
        percentage = self._voltage_to_percentage(voltage)

        return UPSData(
            percentage=percentage,
            voltage=voltage,
            current=current,
            is_charging=is_charging,
            temperature=None,
            time_remaining=None
        )

    async def _read_voltage(self, loop) -> float:
        """Read battery voltage in mV."""
        low = await loop.run_in_executor(
            None,
            self._bus.read_byte_data,
            self._config.i2c_address,
            self._config.voltage_reg_low
        )
        high = await loop.run_in_executor(
            None,
            self._bus.read_byte_data,
            self._config.i2c_address,
            self._config.voltage_reg_high
        )

        if self._config.i2c_address == 0x75:  # IP5209
            # Two's complement with sign bit at 0x20
            raw = ((high & 0x1F) << 8) | low
            if high & 0x20:
                voltage = 2600.0 - (raw * 0.26855)
            else:
                voltage = 2600.0 + (raw * 0.26855)
        else:  # IP5312
            raw = ((high & 0x3F) << 8) | low
            voltage = (raw * 0.26855) + 2600.0

        return voltage

    async def _read_current(self, loop) -> float:
        """Read battery current in Amps.

        Returns:
            Current in Amps (positive = charging, negative = discharging)
        """
        low = await loop.run_in_executor(
            None,
            self._bus.read_byte_data,
            self._config.i2c_address,
            self._config.current_reg_low
        )
        high = await loop.run_in_executor(
            None,
            self._bus.read_byte_data,
            self._config.i2c_address,
            self._config.current_reg_high
        )

        if self._config.i2c_address == 0x75:  # IP5209
            if high & 0x20:
                # Sign bit set = discharging = negative current
                # Sign extend for proper 2's complement
                raw_signed = (((high | 0xC0) << 8) | low)
                # Convert to signed 16-bit
                if raw_signed > 32767:
                    raw_signed -= 65536
                current = raw_signed * 0.745985 / 1000.0  # Result in A
            else:
                # Sign bit clear = charging = positive current
                raw = ((high & 0x1F) << 8) | low
                current = raw * 0.745985 / 1000.0  # Result in A
        else:  # IP5312
            raw = ((high & 0x1F) << 8) | low
            current = raw * 2.68554 / 1000.0  # Result in A
            if high & 0x20:
                current = -current  # Sign bit = discharging

        return current

    async def _read_power_status(self, loop) -> bool:
        """Read power plugged/charging status."""
        status = await loop.run_in_executor(
            None,
            self._bus.read_byte_data,
            self._config.i2c_address,
            self._config.power_status_reg
        )

        if self._config.i2c_address == 0x75:  # IP5209
            return bool(status & self._config.power_plugged_mask)
        else:  # IP5312
            # For IP5312, 0x1F means plugged in
            return status == self._config.power_plugged_mask

    def _voltage_to_percentage(self, voltage_mv: float) -> float:
        """Convert voltage to battery percentage using lookup curve."""
        if voltage_mv >= BATTERY_CURVE[0][0]:
            return 100.0
        if voltage_mv <= BATTERY_CURVE[-1][0]:
            return 0.0

        # Linear interpolation between curve points
        for i in range(len(BATTERY_CURVE) - 1):
            v_high, p_high = BATTERY_CURVE[i]
            v_low, p_low = BATTERY_CURVE[i + 1]

            if v_low <= voltage_mv <= v_high:
                # Interpolate
                ratio = (voltage_mv - v_low) / (v_high - v_low)
                return p_low + ratio * (p_high - p_low)

        return 50.0  # Fallback

    async def is_available(self) -> bool:
        """Check if hardware is available."""
        if not self._available:
            return await self.initialize()
        return True

    def close(self):
        """Close I2C bus."""
        if self._bus:
            self._bus.close()
            self._bus = None
        self._available = False

    def get_model_name(self) -> str:
        """Get detected model name."""
        return self._config.model.value

    # ========== Button Detection ==========

    async def read_button_state(self) -> bool:
        """Read current button GPIO state.

        Returns:
            True if button is pressed
        """
        if not self._bus or not self._available:
            return False

        loop = asyncio.get_event_loop()
        try:
            status = await loop.run_in_executor(
                None,
                self._bus.read_byte_data,
                self._config.i2c_address,
                0x55  # GPIO status register
            )

            if self._config.i2c_address == 0x75:  # IP5209
                # 4-LED models use GPIO4 (bit 4), 2-LED use GPIO1 (bit 1)
                # Default to 4-LED behavior
                return bool(status & 0x10)
            else:  # IP5312
                return bool(status & 0x10)

        except Exception:
            return False

    # ========== RTC Functions (SD3078 at 0x32) ==========

    async def get_rtc_time(self) -> Optional[datetime]:
        """Read current time from RTC.

        Returns:
            datetime object or None if RTC not available
        """
        if not self._bus:
            return None

        loop = asyncio.get_event_loop()
        try:
            # Read 7 bytes starting from register 0x00
            data = await loop.run_in_executor(
                None,
                self._bus.read_i2c_block_data,
                self.RTC_ADDRESS,
                0x00,
                7
            )

            # Parse BCD format: sec, min, hour, weekday, day, month, year
            second = self._bcd_to_int(data[0] & 0x7F)
            minute = self._bcd_to_int(data[1] & 0x7F)
            hour = self._bcd_to_int(data[2] & 0x3F)  # 24-hour format
            day = self._bcd_to_int(data[4] & 0x3F)
            month = self._bcd_to_int(data[5] & 0x1F)
            year = 2000 + self._bcd_to_int(data[6])

            return datetime(year, month, day, hour, minute, second)

        except Exception:
            return None

    async def set_rtc_time(self, dt: datetime) -> bool:
        """Set RTC time.

        Args:
            dt: datetime to set

        Returns:
            True if successful
        """
        if not self._bus:
            return False

        loop = asyncio.get_event_loop()
        try:
            # Enable write mode (CTR2 register 0x10, set WRTC1/WRTC2/WRTC3)
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x10,
                0x80  # Enable write
            )
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x0F,
                0x84  # Enable write
            )

            # Write time data in BCD format
            data = [
                self._int_to_bcd(dt.second),
                self._int_to_bcd(dt.minute),
                self._int_to_bcd(dt.hour) | 0x80,  # 24-hour mode
                self._int_to_bcd(dt.weekday()),
                self._int_to_bcd(dt.day),
                self._int_to_bcd(dt.month),
                self._int_to_bcd(dt.year % 100)
            ]

            await loop.run_in_executor(
                None,
                self._bus.write_i2c_block_data,
                self.RTC_ADDRESS,
                0x00,
                data
            )

            # Disable write mode
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x0F,
                0x00
            )
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x10,
                0x00
            )

            return True

        except Exception:
            return False

    async def set_wake_alarm(self, wake_time: datetime, repeat_weekdays: int = 0) -> bool:
        """Set RTC alarm for scheduled wake-up.

        Args:
            wake_time: Time to wake up
            repeat_weekdays: Bitmask for weekday repeat (0=one-time, 0x7F=daily)

        Returns:
            True if successful
        """
        if not self._bus:
            return False

        loop = asyncio.get_event_loop()
        try:
            # Enable write mode
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x10,
                0x80
            )

            # Write alarm time (registers 0x07-0x0D)
            alarm_data = [
                self._int_to_bcd(wake_time.second),
                self._int_to_bcd(wake_time.minute),
                self._int_to_bcd(wake_time.hour) | 0x80,  # 24-hour mode
                repeat_weekdays,  # Weekday repeat mask
                self._int_to_bcd(wake_time.day),
                self._int_to_bcd(wake_time.month),
                self._int_to_bcd(wake_time.year % 100)
            ]

            await loop.run_in_executor(
                None,
                self._bus.write_i2c_block_data,
                self.RTC_ADDRESS,
                0x07,
                alarm_data
            )

            # Enable alarm (CTR2 register 0x10, set INTAE bit)
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x0E,
                0x07  # Enable hour/minute/second alarm match
            )
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x10,
                0x04  # Enable alarm interrupt
            )

            # Set frequency for auto power-on
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x11,
                0x01  # 1/2Hz for auto power-on
            )

            return True

        except Exception:
            return False

    async def clear_wake_alarm(self) -> bool:
        """Clear/disable wake alarm.

        Returns:
            True if successful
        """
        if not self._bus:
            return False

        loop = asyncio.get_event_loop()
        try:
            # Disable alarm interrupt
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x10,
                0x00
            )
            await loop.run_in_executor(
                None,
                self._bus.write_byte_data,
                self.RTC_ADDRESS,
                0x0E,
                0x00
            )
            return True
        except Exception:
            return False

    # ========== Power Control ==========

    async def force_shutdown(self) -> bool:
        """Force immediate shutdown of the Pi.

        This enables light-load auto-shutdown on the IP5209/IP5312
        which will cut power when the Pi draws minimal current.

        Returns:
            True if command was sent
        """
        if not self._bus or not self._available:
            return False

        loop = asyncio.get_event_loop()
        try:
            if self._config.i2c_address == 0x75:  # IP5209
                # Enable light-load auto-shutdown and force shutdown
                # Register 0x01, set appropriate bits
                await loop.run_in_executor(
                    None,
                    self._bus.write_byte_data,
                    self._config.i2c_address,
                    0x01,
                    0x29  # Enable auto-shutdown
                )
            else:  # IP5312
                # IP5312 has different shutdown mechanism
                await loop.run_in_executor(
                    None,
                    self._bus.write_byte_data,
                    self._config.i2c_address,
                    0x03,
                    0x08  # Force shutdown
                )
            return True
        except Exception:
            return False

    # ========== Helpers ==========

    def _bcd_to_int(self, bcd: int) -> int:
        """Convert BCD byte to integer."""
        return (bcd >> 4) * 10 + (bcd & 0x0F)

    def _int_to_bcd(self, value: int) -> int:
        """Convert integer to BCD byte."""
        return ((value // 10) << 4) | (value % 10)
