import asyncio
import time
from datetime import date
from io import StringIO
from pathlib import Path

from ruamel import yaml

from clarabot_nano_tests.gateway.inter.utility import check_fs_recursive


class BackupTestingObserver:
    SYNC_TRIGGER_TIME = 60 * 60 * 25

    def __init__(
        self,
        testing_client_manager,
        u0mut_name0,
        u0a_sess0,
        u0nano_id0,
        u0wsc0,
        u0_kr,
        remote_path: str,
        drive_path: str,
        room_id: str,
        invalid_room_id: str,
    ):
        self.testing_client_manager = testing_client_manager
        self.u0mut_name0 = u0mut_name0
        self.u0a_sess0 = u0a_sess0
        self.u0nano_id0 = u0nano_id0
        self.u0wsc0 = u0wsc0
        self.u0_kr = u0_kr
        self.remote_path = Path(remote_path)
        self.drive_path = drive_path
        self.path = Path(drive_path, "Nano", "Backup", room_id)
        self.invalid_room_id = invalid_room_id
        self.finished_folder = None
        self._has_invalid_room = False

    @property
    def next_folder(self) -> str:
        if self.finished_folder is None:
            return date.today().strftime("%Y/%m/%d")
        d, _, n = self.finished_folder.rpartition("_")
        if not d:
            d = n
            n = 2
        else:
            n = int(n) + 1
        return f"{d}_{n}"

    def read_status(self, require=False):
        p = self.path / "Status.yml"
        if p.exists():
            d = yaml.YAML(typ="safe", pure=True).load(StringIO(p.read_bytes().decode("utf-8")))
            self.finished_folder = d.get("last_folder")
            return d
        else:
            assert not require

    def write_status(self, status: dict):
        sio = StringIO()
        yaml.YAML(typ="safe", pure=True).dump(status, sio)
        (self.path / "Status.yml").write_bytes(sio.getvalue().encode("utf-8"))

    async def wait_finish(self, folder: str = None, room_id=None, check=True, current_folder=None, timeout=15.0):
        if folder is None:
            folder = self.next_folder
        if room_id is None:  # trigger new sync by moving the timestamps back
            room_id = self.invalid_room_id
            add = not self._has_invalid_room
            self._has_invalid_room = add
        else:
            add = True
        try:  # trigger re-sync
            status = self.read_status(require=False)
            if status:
                await asyncio.sleep(5.0)  # NOTE: FS-LS stress-cache ttl needs to be waited
                status["last_start"] = status["last_start"] - self.SYNC_TRIGGER_TIME
                status["last_finish"] = status["last_finish"] - self.SYNC_TRIGGER_TIME
                if current_folder:
                    status["current_folder"] = current_folder
                self.write_status(status)
        except OSError:
            pass
        if room_id:
            await self.call_backup_config_update(room_id, add=add)
        timeout += time.perf_counter()
        while True:
            try:
                self.read_status()
            except OSError:
                pass
            else:
                if self.finished_folder == folder:
                    break
            assert time.perf_counter() < timeout, f"{self.finished_folder} {folder} {current_folder}"
            await asyncio.sleep(0.1)
        if check:  # check data matching
            check_fs_recursive(self.remote_path, self.path / folder)

    async def call_backup_config_update(self, room_id: str, add=True):
        req = {"type": "A-D-ADD-BUP" if add else "A-D-DEL-BUP", "path": self.drive_path, "target_room_id": room_id}
        await self.testing_client_manager.single_response_admin_request(
            req, self.u0mut_name0, self.u0a_sess0, None, self.u0nano_id0, self.u0wsc0, self.u0_kr, self.u0_kr
        )
