from __future__ import annotations

import re
import threading
import time
from pathlib import Path
from typing import Optional


class LogWatcher:
    def __init__(self, log_path: str, poll_interval: float = 0.1) -> None:
        self.log_path = Path(log_path)
        self.poll_interval = poll_interval
        self._lines: list[str] = []
        self._markers: dict[str, int] = {}
        self._lock = threading.Lock()
        self._stop = threading.Event()
        self._thread: Optional[threading.Thread] = None

    def start(self) -> None:
        self.log_path.parent.mkdir(parents=True, exist_ok=True)
        self.log_path.touch(exist_ok=True)
        self._stop.clear()
        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()

    def stop(self) -> None:
        self._stop.set()
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=2)

    def _run(self) -> None:
        f = self.log_path.open("r", encoding="utf-8", errors="replace")
        f.seek(0, 2)
        try:
            while not self._stop.is_set():
                pos = f.tell()
                line = f.readline()
                if not line:
                    if self.log_path.exists() and self.log_path.stat().st_size < pos:
                        f.close()
                        f = self.log_path.open("r", encoding="utf-8", errors="replace")
                    time.sleep(self.poll_interval)
                    continue
                with self._lock:
                    self._lines.append(line.rstrip("\n"))
        finally:
            f.close()

    def set_marker(self, name: str) -> None:
        with self._lock:
            self._markers[name] = len(self._lines)

    def get_lines_since(self, marker: str) -> list[str]:
        with self._lock:
            idx = self._markers.get(marker, 0)
            return list(self._lines[idx:])

    def get_all_lines(self) -> list[str]:
        with self._lock:
            return list(self._lines)

    def search(self, pattern: str, since_marker: Optional[str] = None) -> list[str]:
        regex = re.compile(pattern)
        lines = self.get_all_lines() if since_marker is None else self.get_lines_since(since_marker)
        return [line for line in lines if regex.search(line)]

    def error_lines(self, since_marker: Optional[str] = None) -> list[str]:
        lines = self.get_all_lines() if since_marker is None else self.get_lines_since(since_marker)
        return [line for line in lines if "[ERROR]" in line]

    def warning_lines(self, since_marker: Optional[str] = None) -> list[str]:
        lines = self.get_all_lines() if since_marker is None else self.get_lines_since(since_marker)
        return [line for line in lines if "[WARN" in line]

    def has_errors(self, since_marker: Optional[str] = None) -> bool:
        return len(self.error_lines(since_marker)) > 0
