"""Tests for rbsp.runtime.platform -- file system, process manager, platform info."""

import os
import sys
import threading
import time
import unittest

sys.path.insert(0, os.path.dirname(__file__))
from conftest import RBSPTestCase, FIXED_TIMESTAMP, SAMPLE_TEXTS

from rbsp.runtime.platform import FileSystem, ProcessManager, PLATFORM, PlatformInfo


class TestPlatformInfo(unittest.TestCase):
    """Tests for PLATFORM singleton and PlatformInfo.detect()."""

    def test_platform_is_platform_info(self):
        self.assertIsInstance(PLATFORM, PlatformInfo)

    def test_system_is_string(self):
        self.assertIsInstance(PLATFORM.system, str)
        self.assertTrue(len(PLATFORM.system) > 0)

    def test_system_known_value(self):
        self.assertIn(PLATFORM.system, ("Windows", "Linux", "Darwin", "Freebsd"))

    def test_path_separator(self):
        self.assertEqual(PLATFORM.path_separator, os.sep)

    def test_pid_is_positive(self):
        self.assertGreater(PLATFORM.pid, 0)

    def test_cpu_count_is_positive(self):
        self.assertGreaterEqual(PLATFORM.cpu_count, 1)

    def test_is_windows_matches_system(self):
        if PLATFORM.system == "Windows":
            self.assertTrue(PLATFORM.is_windows)
        else:
            self.assertFalse(PLATFORM.is_windows)

    def test_is_linux_matches_system(self):
        if PLATFORM.system == "Linux":
            self.assertTrue(PLATFORM.is_linux)
        else:
            self.assertFalse(PLATFORM.is_linux)

    def test_is_macos_matches_system(self):
        if PLATFORM.system == "Darwin":
            self.assertTrue(PLATFORM.is_macos)
        else:
            self.assertFalse(PLATFORM.is_macos)

    def test_frozen_cannot_modify(self):
        with self.assertRaises(AttributeError):
            PLATFORM.system = "FakeOS"

    def test_detect_creates_new_instance(self):
        info = PlatformInfo.detect()
        self.assertIsInstance(info, PlatformInfo)
        self.assertEqual(info.system, PLATFORM.system)


class TestFileSystemEnsureDir(RBSPTestCase):
    """Tests for FileSystem.ensure_dir()."""

    def test_creates_directory(self):
        path = self.tmp_path("newdir")
        result = FileSystem.ensure_dir(path)
        self.assertTrue(os.path.isdir(path))
        self.assertEqual(str(result), str(result))  # returns Path

    def test_creates_nested_directories(self):
        path = os.path.join(self.tmp_dir, "a", "b", "c")
        FileSystem.ensure_dir(path)
        self.assertTrue(os.path.isdir(path))

    def test_idempotent(self):
        path = self.tmp_path("existdir")
        FileSystem.ensure_dir(path)
        FileSystem.ensure_dir(path)  # should not raise
        self.assertTrue(os.path.isdir(path))


class TestFileSystemWalkFiles(RBSPTestCase):
    """Tests for FileSystem.walk_files()."""

    def setUp(self):
        super().setUp()
        # Create a directory structure
        os.makedirs(os.path.join(self.tmp_dir, "src"), exist_ok=True)
        os.makedirs(os.path.join(self.tmp_dir, "node_modules"), exist_ok=True)
        self.make_sample_file(os.path.join("src", "app.py"), "print('hello')")
        self.make_sample_file(os.path.join("src", "util.js"), "console.log('hi')")
        self.make_sample_file(os.path.join("node_modules", "pkg.js"), "module.exports = {}")
        self.make_sample_file("readme.txt", "readme content")

    def test_walk_finds_files(self):
        files = list(FileSystem.walk_files(self.tmp_dir))
        self.assertGreater(len(files), 0)

    def test_walk_filters_by_extension(self):
        files = list(FileSystem.walk_files(self.tmp_dir, extensions={".py"}))
        for f in files:
            self.assertEqual(f.suffix, ".py")

    def test_walk_ignores_directories(self):
        files = list(FileSystem.walk_files(self.tmp_dir, ignored_dirs={"node_modules"}))
        for f in files:
            self.assertNotIn("node_modules", str(f))

    def test_walk_returns_path_objects(self):
        files = list(FileSystem.walk_files(self.tmp_dir))
        for f in files:
            self.assertTrue(hasattr(f, "suffix"))  # Path-like


class TestFileSystemReadText(RBSPTestCase):
    """Tests for FileSystem.read_text()."""

    def test_read_existing_file(self):
        path = self.make_sample_file("test.txt", "hello world")
        content = FileSystem.read_text(path)
        self.assertEqual(content, "hello world")

    def test_read_nonexistent_returns_empty(self):
        content = FileSystem.read_text(os.path.join(self.tmp_dir, "nonexistent.txt"))
        self.assertEqual(content, "")

    def test_read_utf8(self):
        path = self.make_sample_file("unicode.txt", "caf\u00e9 \u2603")
        content = FileSystem.read_text(path)
        self.assertIn("caf\u00e9", content)


class TestFileSystemFileInfo(RBSPTestCase):
    """Tests for FileSystem.file_info()."""

    def test_file_info_returns_dict(self):
        path = self.make_sample_file("info_test.txt", "some content")
        info = FileSystem.file_info(path)
        self.assertIsInstance(info, dict)

    def test_file_info_has_expected_keys(self):
        path = self.make_sample_file("info_keys.txt", "content")
        info = FileSystem.file_info(path)
        for key in ("path", "size", "modified", "is_file", "is_dir"):
            self.assertIn(key, info)

    def test_file_info_size_correct(self):
        content = "twelve chars"
        path = self.make_sample_file("size_test.txt", content)
        info = FileSystem.file_info(path)
        self.assertEqual(info["size"], os.path.getsize(path))

    def test_file_info_is_file_true(self):
        path = self.make_sample_file("flag_test.txt", "x")
        info = FileSystem.file_info(path)
        self.assertTrue(info["is_file"])
        self.assertFalse(info["is_dir"])

    def test_file_info_nonexistent_returns_none(self):
        info = FileSystem.file_info(os.path.join(self.tmp_dir, "nope.txt"))
        self.assertIsNone(info)


class TestFileSystemRelativeTo(RBSPTestCase):
    """Tests for FileSystem.relative_to()."""

    def test_relative_path(self):
        base = self.tmp_dir
        full = os.path.join(self.tmp_dir, "a", "b.py")
        rel = FileSystem.relative_to(full, base)
        self.assertEqual(rel, "a/b.py")

    def test_same_path(self):
        rel = FileSystem.relative_to(self.tmp_dir, self.tmp_dir)
        self.assertEqual(rel, ".")

    def test_unrelated_path_returns_string(self):
        # When path cannot be made relative, returns str of path
        result = FileSystem.relative_to("/some/other/path", self.tmp_dir)
        self.assertIsInstance(result, str)


class TestProcessManager(unittest.TestCase):
    """Tests for ProcessManager thread management."""

    def setUp(self):
        self.pm = ProcessManager()

    def test_start_thread_returns_true(self):
        event = threading.Event()
        def worker():
            event.wait(timeout=5)
        result = self.pm.start_thread("test-worker", worker)
        self.assertTrue(result)
        event.set()
        self.pm.stop_thread("test-worker")

    def test_start_duplicate_returns_false(self):
        event = threading.Event()
        def worker():
            event.wait(timeout=5)
        self.pm.start_thread("dup-worker", worker)
        result = self.pm.start_thread("dup-worker", worker)
        self.assertFalse(result)
        event.set()
        self.pm.stop_thread("dup-worker")

    def test_is_running_true(self):
        event = threading.Event()
        def worker():
            event.wait(timeout=5)
        self.pm.start_thread("running-check", worker)
        self.assertTrue(self.pm.is_running("running-check"))
        event.set()
        self.pm.stop_thread("running-check")

    def test_is_running_false_for_unknown(self):
        self.assertFalse(self.pm.is_running("nonexistent"))

    def test_stop_thread_returns_true(self):
        event = threading.Event()
        def worker():
            event.wait(timeout=5)
        self.pm.start_thread("stop-test", worker)
        event.set()
        result = self.pm.stop_thread("stop-test", timeout=2.0)
        self.assertTrue(result)

    def test_stop_nonexistent_returns_true(self):
        result = self.pm.stop_thread("no-such-thread")
        self.assertTrue(result)

    def test_is_running_false_after_stop(self):
        event = threading.Event()
        def worker():
            event.wait(timeout=5)
        self.pm.start_thread("lifecycle", worker)
        event.set()
        self.pm.stop_thread("lifecycle", timeout=2.0)
        # Give thread a moment to finish
        time.sleep(0.1)
        self.assertFalse(self.pm.is_running("lifecycle"))


if __name__ == "__main__":
    unittest.main()
