import logging
import time
from datetime import datetime
from typing import Protocol, Union

from rich import print
from rich.live import Live
from rich.panel import Panel

from oeleo.layouts import confirm, create_layout
from oeleo.reporters import LayoutReporter
from oeleo.workers import WorkerBase

log = logging.getLogger("oeleo")


class ScheduleAborted(Exception):
    """Raised when the user aborts the run."""

    pass


class SchedulerBase(Protocol):
    worker: WorkerBase = None
    state: dict = None
    update_db: bool = True
    force: bool = False

    def _setup(self):
        ...

    def start(self):
        ...

    def _update_db(self):
        ...

    # consider adding a close_all or clean_up method


class SimpleScheduler(SchedulerBase):
    def __init__(
        self,
        worker: WorkerBase,
        run_interval_time=43_200,
        max_run_intervals=1000,
        update_db=True,
        force=False,
    ):
        self.worker = worker
        self.state = {"iterations": 0}
        # self.update_interval = 3_600  # not used
        self.run_interval_time = run_interval_time
        self.max_run_intervals = max_run_intervals
        self.update_db: bool = update_db
        self.force: bool = force
        # self._last_update = None
        self._sleep_interval = max(run_interval_time / 10, 1)
        self._last_run = None
        self._run_counter = 0

    def _setup(self):
        log.debug("setting up scheduler")
        self.worker.connect_to_db()
        self.worker.check(update_db=self.update_db, force=self.force)
        # self._last_update = datetime.now()

    def start(self):
        log.debug("***** START:")
        self._setup()
        while True:
            self.state["iterations"] += 1
            log.debug(f"ITERATING ({self.state['iterations']})")

            self.worker.filter_local()
            self.worker.run()
            self._last_run = datetime.now()
            self._run_counter += 1

            if self._run_counter >= self.max_run_intervals:
                log.debug("-> BREAK")
                break

            used_time = 0.0

            while used_time < self.run_interval_time:
                time.sleep(self._sleep_interval)
                used_time = (datetime.now() - self._last_run).total_seconds()
        self.worker.close()

    def _update_db(self):
        pass


class RichScheduler(SchedulerBase):
    def __init__(
        self,
        worker: WorkerBase,
        run_interval_time=43_200,
        max_run_intervals=1000,
        update_db=True,
        force=False,
        auto_accept_check=False,
    ):
        self.worker = worker
        self.state = {"iterations": 0}
        # self.update_interval = 3_600  # not used
        self.run_interval_time: Union[float, int] = run_interval_time
        self.max_run_intervals: int = max_run_intervals
        self.update_db: bool = update_db
        self.force: bool = force
        self.auto_accept_check: bool = (
            auto_accept_check  # nice to have when testing with pytest
        )
        # self._last_update = None
        self._sleep_interval = 1  # second
        self._last_run = None
        self._run_counter = 0
        self.layout = None

    def _setup(self):
        log.debug("setting up scheduler")
        self.layout = create_layout("rich_scheduler")
        self.worker.reporter = LayoutReporter(self.layout)

    def start(self):
        log.debug("***** START:")
        try:
            self._setup()

            with Live(self.layout, refresh_per_second=20, screen=True):
                self.layout["middle_header"].update(
                    Panel(f"(L) {self.worker.local_connector.directory}")
                )
                self.layout["right_header"].update(
                    Panel(f"(E) {self.worker.external_connector.directory}")
                )
                self.worker.connect_to_db()

                self.layout["left_footer"].update(Panel(f"CHECK..."))
                self.worker.check(update_db=self.update_db, force=self.force)
                if not self.auto_accept_check:
                    if not confirm(self.layout):
                        raise ScheduleAborted

                self.worker.reporter.clear()

                status_symbol = ":smiley:"
                self.layout["status_footer"].update(Panel(status_symbol))
                while True:

                    time.sleep(0.2)
                    self.state["iterations"] += 1
                    log.debug(f"ITERATING ({self.state['iterations']})")
                    self.layout["left_footer"].update(
                        Panel(f"I:{self.state['iterations']:06}")
                    )
                    self.worker.reporter.report(
                        f"\n[cyan bold]NEW ITERATION:[/cyan bold] {self.state['iterations']:06}/{self.max_run_intervals:06}"
                    )
                    self.layout["middle_footer"].update(Panel("filter local"))
                    self.worker.filter_local()
                    self.layout["middle_footer"].update(Panel("run"))
                    self.worker.run()
                    self._last_run = datetime.now()
                    self._run_counter += 1

                    if self._run_counter == self.max_run_intervals - 2:
                        status_symbol = ":old_man_medium_skin_tone:"
                        self.layout["status_footer"].update(Panel(status_symbol))

                    if self._run_counter >= self.max_run_intervals:
                        self.layout["middle_footer"].update(Panel("done"))
                        status_symbol = ":skull:"
                        self.layout["status_footer"].update(Panel(status_symbol))

                        log.debug("-> BREAK")
                        time.sleep(0.2)
                        break

                    used_time = 0.0
                    self.layout["middle_footer"].update(
                        Panel(f"Idle for {round(used_time)}/{self.run_interval_time} s")
                    )
                    self.worker.reporter.report(".")

                    self.layout["status_footer"].update(Panel(":sleeping:"))
                    while used_time < self.run_interval_time:
                        time.sleep(self._sleep_interval)
                        self.worker.reporter.report(".", same_line=True)
                        used_time = (datetime.now() - self._last_run).total_seconds()
                        self.layout["middle_footer"].update(
                            Panel(
                                f"Idle for {round(used_time)}/{self.run_interval_time} s"
                            )
                        )
                    self.layout["status_footer"].update(Panel(status_symbol))
        except (KeyboardInterrupt, ScheduleAborted):
            print("[bold red]Interrupted by user ...exiting")

        else:
            print("[bold green]Finished ...exiting")

        finally:
            self.worker.close()

        for i, line in enumerate(self.worker.reporter.lines):
            print(f"[{i:03}] {line}")

    def _update_db(self):
        pass
