import argparse
import json
import os
import time
from collections import deque
from itertools import chain
from pathlib import Path
from typing import Dict

from clarabot_nano_common.utility.path.common import normalize_absolute_path

from clarabot_nano.indexer.observer import (
    Observer,
    FileSystemEvent,
    FileSystemMovedEvent,
    EVENT_TYPE_DELETED,
    EVENT_TYPE_CREATED,
    EVENT_TYPE_MODIFIED,
    EVENT_TYPE_CLOSED,
)


class _TestObserverEventHandler(object):
    def __init__(self, directory: Path):
        self.directory = directory
        self.create_expected: Dict[str, float] = dict()
        self.modify_expected: Dict[str, float] = dict()
        self.delete_expected: Dict[str, float] = dict()
        self.moved_expected: Dict[str, float] = dict()
        self.duplicate = 0
        self.unexpected = 0
        self.delays = []
        self.first_delay = None
        self.duplicate_q = deque()

    def _dup_q(self, p):
        self.duplicate_q.append(p)
        while len(self.duplicate_q) > 1:
            self.duplicate_q.popleft()

    def dispatch(self, event: FileSystemEvent):
        if isinstance(event, FileSystemMovedEvent):
            expected = self.moved_expected
            p = "///".join([normalize_absolute_path(event.src_path), normalize_absolute_path(event.dest_path)])
        elif event.event_type == EVENT_TYPE_DELETED:
            expected = self.delete_expected
            p = normalize_absolute_path(event.src_path)
        elif event.event_type == EVENT_TYPE_CREATED:
            expected = self.create_expected
            p = normalize_absolute_path(event.src_path)
        elif event.event_type == EVENT_TYPE_MODIFIED:
            if event.is_directory:
                return  # n/a
            expected = self.modify_expected
            p = normalize_absolute_path(event.src_path)
        elif event.event_type == EVENT_TYPE_CLOSED:
            return  # n/a
        else:
            print("Unhandled event:", event)
            return
        if p not in expected:
            if p in self.duplicate_q:
                print("Duplicate event:", event)
                self.duplicate += 1
            else:
                print("Unexpected event:", event)
                self.unexpected += 1
        else:
            self._dup_q(p)
            print("Expected event:", event)
            if self.first_delay is None:
                self.first_delay = time.perf_counter() - min(
                    chain(
                        self.create_expected.values(),
                        self.modify_expected.values(),
                        self.delete_expected.values(),
                        self.moved_expected.values(),
                    )
                )
            self.delays.append(time.perf_counter() - expected.pop(p))

    def create(self, i: int):
        p = normalize_absolute_path(self.directory / f"_file_{i}")
        self.create_expected[p] = time.perf_counter()
        self.modify_expected[p] = time.perf_counter()
        with open(p, "wb") as f:
            f.write(os.urandom(8))


def main():
    parser = argparse.ArgumentParser("Clarabot Nano - FS event observer testing")
    parser.add_argument("directory")
    parser.add_argument("cycles", type=int)
    parser.add_argument("wait", type=float)

    args = parser.parse_args()
    directory = Path(args.directory)
    if not directory.is_dir():
        raise Exception("directory invalid")

    event_handler = _TestObserverEventHandler(directory)

    observer = Observer()
    observer.schedule(event_handler, str(directory), recursive=True)
    observer.start()
    try:
        time.sleep(args.wait)
        for i in range(args.cycles):
            event_handler.create(i)
            time.sleep(args.wait)
    finally:
        observer.stop()
        observer.stopped_event.wait(10)
        (directory / "_result.json").write_bytes(
            json.dumps(
                {
                    "first_delay": event_handler.first_delay,
                    "unexpected": event_handler.unexpected,
                    "delays": event_handler.delays,
                    "missed": list(
                        chain(
                            event_handler.create_expected.keys(),
                            event_handler.modify_expected.keys(),
                            event_handler.delete_expected.keys(),
                            event_handler.moved_expected.keys(),
                        )
                    ),
                    "duplicate": event_handler.duplicate,
                }
            ).encode("utf-8")
        )


if __name__ == "__main__":
    main()
