import logging
import random

from styles.base import AlarmStyle

# State Machine states for the puzzle flow.
STATE_RINGING = 0
STATE_WAIT_BEFORE_BLINK = 1
STATE_BLINKING = 2
STATE_WAIT_FOR_INPUT = 3
STATE_EVALUATING = 4
STATE_WAIT_BEFORE_RETRY = 5

BLINK_INTERVAL = 0.3


class BlinkStyle(AlarmStyle):
    """The count-the-blinks puzzle.

    Press once to start, watch the LED blink 1-7 times, then press the button
    exactly that many times. A wrong count starts a fresh sequence.
    """

    def __init__(self, set_led):
        super().__init__(set_led)
        self.state = STATE_RINGING
        self.target_blinks = 0
        self.user_presses = 0
        self.last_interaction_time = 0
        self.button_was_pressed = False
        self._blink_phase = 0
        self._blink_phases = 0
        self._blink_next_toggle: float | None = None

    def update(self, now, is_pressed):
        button_just_pressed = False
        if is_pressed and not self.button_was_pressed:
            self.button_was_pressed = True
            button_just_pressed = True
        elif not is_pressed and self.button_was_pressed:
            self.button_was_pressed = False

        if self.state == STATE_RINGING:
            if button_just_pressed:
                logging.info(
                    "Alarm button pressed! Puzzle started. Waiting 3 seconds..."
                )
                self.state = STATE_WAIT_BEFORE_BLINK
                self.last_interaction_time = now

        elif self.state == STATE_WAIT_BEFORE_BLINK:
            if now - self.last_interaction_time >= 3.0:
                self.target_blinks = random.randint(1, 7)
                logging.info(f"Blinking {self.target_blinks} times...")
                self.state = STATE_BLINKING
                self._blink_phase = 0
                self._blink_phases = self.target_blinks * 2
                self._blink_next_toggle = now + BLINK_INTERVAL
                self.set_led(True)

        elif self.state == STATE_BLINKING:
            # Non-blocking blink: toggle the LED at fixed intervals so the
            # runner keeps sampling the button and can be interrupted.
            if self._blink_next_toggle is None:
                # Safety for direct state assignment (e.g. tests).
                self._blink_phase = 0
                self._blink_phases = self.target_blinks * 2
                self._blink_next_toggle = now + BLINK_INTERVAL
                self.set_led(True)

            while now >= self._blink_next_toggle:
                self._blink_phase += 1
                self._blink_next_toggle += BLINK_INTERVAL
                if self._blink_phase >= self._blink_phases:
                    self.set_led(False)
                    self._blink_phase = 0
                    self._blink_phases = 0
                    self._blink_next_toggle = None
                    logging.info("Blinking finished. Waiting for input...")
                    self.state = STATE_WAIT_FOR_INPUT
                    self.user_presses = 0
                    self.last_interaction_time = now
                    self.button_was_pressed = is_pressed
                    break
                else:
                    self.set_led(self._blink_phase % 2 == 0)

        elif self.state == STATE_WAIT_FOR_INPUT:
            self.set_led(is_pressed)

            if button_just_pressed:
                self.user_presses += 1
                logging.info(f"Button pressed: {self.user_presses} times")

            if is_pressed:
                self.last_interaction_time = now

            if not is_pressed and (now - self.last_interaction_time >= 3.0):
                self.state = STATE_EVALUATING

        elif self.state == STATE_EVALUATING:
            logging.info(
                f"Evaluation: Target={self.target_blinks}, "
                f"Entered={self.user_presses}"
            )
            if self.user_presses == self.target_blinks:
                logging.info("Puzzle solved correctly! Alarm clock is stopping.")
                self.set_led(False)
                return False  # Signal the runner to stop.
            else:
                logging.info("Incorrect input! Waiting before retrying...")
                self.state = STATE_WAIT_BEFORE_RETRY
                self.last_interaction_time = now
                self.set_led(False)

        elif self.state == STATE_WAIT_BEFORE_RETRY:
            if now - self.last_interaction_time >= 5.0:
                self.target_blinks = random.randint(1, 7)
                logging.info(f"New attempt! Blinking {self.target_blinks} times...")
                self.state = STATE_BLINKING

        return True  # Keep running


if __name__ == "__main__":
    # Self-check: a correct blink sequence stops the alarm.
    style = BlinkStyle(lambda on: None)

    assert style.update(100.0, True) is True
    assert style.state == STATE_WAIT_BEFORE_BLINK

    import random as _random

    _random.randint = lambda a, b: 3  # force 3 blinks
    style.update(103.1, False)
    assert style.state == STATE_BLINKING and style.target_blinks == 3

    style.update(105.0, False)
    assert style.state == STATE_WAIT_FOR_INPUT

    for t in (105.5, 106.0, 106.5):
        style.update(t, True)
        style.update(t + 0.05, False)
    assert style.user_presses == 3

    style.update(109.7, False)
    assert style.update(109.8, False) is False  # solved -> stop
    print("blink self-check ok")
