"""Offline command-logic exercise. Python 3.10+, standard library only.

This is NOT SSH, encryption, login verification, a network service, GPIO code
or a production controller. The caller-supplied authentication/role values
only model decisions that a real system must establish securely elsewhere.
The stored state is a request; no physical light is measured or driven.

Copyright (c) 2026 LCENJ. MIT License:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import unittest


class LightRequestModel:
    def __init__(self):
        self.requested_on = False

    def apply(self, command, *, authenticated, role):
        if not authenticated:
            raise PermissionError("Model user is not authenticated")
        permissions = {"viewer": {"status"}, "operator": {"on", "off", "status"}}
        if role not in permissions:
            raise PermissionError("Unknown model role")
        # Strip line endings only; do not evaluate code or run a shell.
        cmd = command.rstrip("\r\n")
        if cmd not in {"on", "off", "status"}:
            raise ValueError("Unknown command")
        if cmd not in permissions[role]:
            raise PermissionError("Command is not allowed for this role")
        if cmd == "on":
            self.requested_on = True
        elif cmd == "off":
            self.requested_on = False
        return {"requested_state": "on" if self.requested_on else "off",
                "physical_light": None}


class CommandTests(unittest.TestCase):
    def setUp(self):
        self.model = LightRequestModel()

    def operator(self, cmd):
        return self.model.apply(cmd, authenticated=True, role="operator")

    def test_unauthenticated_cannot_change_output(self):
        with self.assertRaises(PermissionError):
            self.model.apply("on", authenticated=False, role="operator")
        self.assertFalse(self.model.requested_on)

    def test_viewer_cannot_control(self):
        with self.assertRaises(PermissionError):
            self.model.apply("on", authenticated=True, role="viewer")
        self.assertFalse(self.model.requested_on)

    def test_viewer_can_read_request_not_physical_state(self):
        result = self.model.apply("status", authenticated=True, role="viewer")
        self.assertEqual(result, {"requested_state": "off", "physical_light": None})

    def test_unknown_role_cannot_control(self):
        with self.assertRaises(PermissionError):
            self.model.apply("on", authenticated=True, role="stranger")
        self.assertFalse(self.model.requested_on)

    def test_repeated_on_is_not_toggle(self):
        self.operator("on")
        self.operator("on")
        self.assertTrue(self.model.requested_on)

    def test_off_and_repeated_off(self):
        self.operator("on")
        self.operator("off")
        self.operator("off")
        self.assertFalse(self.model.requested_on)

    def test_unknown_command_preserves_request(self):
        self.operator("on")
        with self.assertRaises(ValueError):
            self.operator("toggle")
        self.assertTrue(self.model.requested_on)

    def test_shell_like_text_is_not_executed(self):
        with self.assertRaises(ValueError):
            self.operator("on; off")
        self.assertFalse(self.model.requested_on)

    def test_line_ending_and_command_byte_count(self):
        self.assertEqual(len(b"on\n"), 3)
        self.assertEqual(self.operator("on\n")["requested_state"], "on")


if __name__ == "__main__":
    example = LightRequestModel()
    for command in ["on", "on", "status"]:
        print(command, example.apply(command, authenticated=True, role="operator"))
    unittest.main(verbosity=2)
