import unittest
from unittest.mock import patch, MagicMock
from modules.user.data.user_data import UserData
from modules.user.exceptions.user.user_create_error import UserCreateError
from modules.user.exceptions.user.user_search_error import UserSearchError
from modules.user.exceptions.user_ticket_name_error import UserTicketNameError
from modules.user.exceptions.user_ticket_password_error import UserTicketPasswordError
from modules.user.exceptions.user_ticket_username_error import UserTicketUsernameError
from modules.user.exceptions.user.user_update_error import UserUpdateError
from modules.user.managers.user_manager import UserManager
from modules.user.managers.user_status_manager import UserStatusManager
from modules.user.objects.tickets.name_ticket import NameTicket
from modules.user.objects.tickets.password_ticket import PasswordTicket
from modules.user.objects.user import User
from modules.user.objects.user_status import UserStatus
from modules.user.objects.tickets.username_ticket import UsernameTicket
from modules.util.managers.postgres_conn_manager import PostgresConnManager
from modules.util.objects.data_list import DataList
from modules.util.objects.result import Result


class UserManagerTest(unittest.TestCase):
    @classmethod
    @patch("modules.util.managers.postgres_conn_manager.PostgresConnManager")
    @patch("modules.user.managers.user_status_manager.UserStatusManager")
    def setUpClass(cls, postgres_conn_manager, user_status_manager) -> None:
        cls.postgres_conn_manager: PostgresConnManager = postgres_conn_manager
        cls.user_status_manager: UserStatusManager = user_status_manager

        cls.status_active = UserStatus(1, "ACTIVE", "Active status")
        cls.status_disabled = UserStatus(2, "DISABLED", "Disabled status")
        cls.user_status_manager.get_all = MagicMock(return_value=DataList(
            "USER_STATUSES",
            [cls.status_active, cls.status_disabled],
            "id",
            "const"
        ))

        cls.user_manager: UserManager = UserManager(
            user_data=UserData(
                postgres_conn_manager=cls.postgres_conn_manager
            ),
            user_status_manager=cls.user_status_manager
        )

    def test_create_creates_user(self):
        username = "username"
        password = "<PASSWORD>"
        first_name = "John"
        last_name = "Johnson"
        data = {
            "id": 1,
            "uuid": "ERT-123",
            "username": username,
            "first_name": first_name,
            "last_name": last_name,
            "status_id": self.status_active.get_id()
        }
        self.postgres_conn_manager.insert = MagicMock(return_value=Result(True))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True, "", [data]))
        user = self.user_manager.create(
            UsernameTicket(username),
            PasswordTicket(password),
            NameTicket(first_name, last_name)
        )
        self.postgres_conn_manager.insert.assert_called_once()
        self.postgres_conn_manager.select.assert_called_once()
        self.assertEqual(data["id"], user.get_id())
        self.assertEqual(data["uuid"], user.get_uuid())
        self.assertEqual(data["username"], user.get_username())
        self.assertEqual(data["first_name"], user.get_first_name())
        self.assertEqual(data["last_name"], user.get_last_name())
        self.assertEqual(data["status_id"], user.get_status().get_id())

    def test_user_ticket_fails_on_bad_username(self):
        username_good_1 = "username"
        username_good_2 = "username123"
        username_good_3 = "123username"
        username_bad_1 = "123"
        username_bad_2 = "UserName123"
        username_bad_3 = "username#123"

        try:
            UsernameTicket(username_good_1)
            UsernameTicket(username_good_2)
            UsernameTicket(username_good_3)
        except Exception as e:
            self.fail(str(e))

        with self.assertRaises(UserTicketUsernameError):
            UsernameTicket(username_bad_1)
            self.fail("Did not fail on bad 1")

        with self.assertRaises(UserTicketUsernameError):
            UsernameTicket(username_bad_2)
            self.fail("Did not fail on bad 2")

        with self.assertRaises(UserTicketUsernameError):
            UsernameTicket(username_bad_3)
            self.fail("Did not fail on bad 3")

    def test_user_ticket_fails_on_bad_password(self):
        password_good = "<PASSWORD>"
        password_bad_1 = "<PASSWORD>"
        password_bad_2 = "Password#"
        password_bad_3 = "<PASSWORD>"

        try:
            PasswordTicket(password_good)
        except Exception as e:
            self.fail(str(e))

        with self.assertRaises(UserTicketPasswordError):
            PasswordTicket(password_bad_1)
            self.fail("Did not fail on bad 1")

        with self.assertRaises(UserTicketPasswordError):
            PasswordTicket(password_bad_2)
            self.fail("Did not fail on bad 2")

        with self.assertRaises(UserTicketPasswordError):
            PasswordTicket(password_bad_3)
            self.fail("Did not fail on bad 3")

    def test_user_ticket_fails_on_bad_name(self):
        with self.assertRaises(UserTicketNameError):
            NameTicket("", "Johnson")
            self.fail("Did not fail on first name empty")

        with self.assertRaises(UserTicketNameError):
            NameTicket("John", "")
            self.fail("Did not fail on last name empty")

    def test_create_fails_on_create_error(self):
        self.postgres_conn_manager.insert = MagicMock(return_value=Result(False))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True))
        with self.assertRaises(UserCreateError):
            self.user_manager.create(
                UsernameTicket("username123"),
                PasswordTicket("<PASSWORD>"),
                NameTicket("John", "Johnson")
            )
            self.fail("Did not fail")
        self.postgres_conn_manager.insert.assert_called_once()
        self.postgres_conn_manager.select.assert_not_called()

    def test_get_fails_on_search_error(self):
        self.postgres_conn_manager.select = MagicMock(return_value=Result(False))
        with self.assertRaises(UserSearchError):
            self.user_manager.get(1)
            self.fail("Did not fail")
        self.postgres_conn_manager.select.assert_called_once()

    def test_update_updates_user(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(True))
        user_id = 1
        first_name = "John"
        last_name = "Johnson"
        data = {
            "id": user_id,
            "uuid": "ERT-123",
            "username": "username123",
            "first_name": first_name,
            "last_name": last_name,
            "status_id": self.status_active.get_id()
        }
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True, "", [data]))
        try:
            user = self.user_manager.update(user_id, NameTicket("John", "Johnson"))
            self.postgres_conn_manager.query.assert_called_once()
            self.postgres_conn_manager.select.assert_called_once()
            self.assertEqual(data["first_name"], user.get_first_name())
            self.assertEqual(data["last_name"], user.get_last_name())
            self.assertEqual(data["id"], user.get_id())
        except Exception as e:
            self.fail(str(e))

    def test_update_fails_on_update(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(False))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True))
        with self.assertRaises(UserUpdateError):
            self.user_manager.update(1, NameTicket("John", "Johnson"))
            self.fail("Did not fail")
        self.postgres_conn_manager.query.assert_called_once()
        self.postgres_conn_manager.select.assert_not_called()

    def test_update_password_updates_password(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(True))
        try:
            self.user_manager.update_password(1, PasswordTicket("<PASSWORD>"))
            self.postgres_conn_manager.query.assert_called_once()
        except Exception as e:
            self.fail(str(e))

    def test_update_password_fails_on_update(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(False))
        with self.assertRaises(UserUpdateError):
            self.user_manager.update_password(1, PasswordTicket("<PASSWORD>"))
            self.fail("Did not fail")
        self.postgres_conn_manager.query.assert_called_once()

    def test_update_status_updates_status(self):
        status_id = self.status_disabled.get_id()
        user_id = 1
        self.postgres_conn_manager.query = MagicMock(return_value=Result(True))
        data = {
            "id": 1,
            "uuid": "ERT-123",
            "username": "username123",
            "first_name": "John",
            "last_name": "Johnson",
            "status_id": status_id
        }
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True, "", [data]))
        try:
            user = self.user_manager.update_status(user_id, status_id)
            self.postgres_conn_manager.query.assert_called_once()
            self.postgres_conn_manager.select.assert_called_once()
            self.assertEqual(data["status_id"], user.get_status().get_id())
            self.assertEqual(data["id"], user.get_id())
        except Exception as e:
            self.fail(str(e))

    def test_update_status_fails_on_update_error(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(False))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True))
        with self.assertRaises(UserUpdateError):
            self.user_manager.update_status(1, self.status_disabled.get_id())
            self.fail("Did not fail")
        self.postgres_conn_manager.query.assert_called_once()
        self.postgres_conn_manager.select.assert_not_called()

    def test_search_searches_users(self):
        users = [
            {
                "id": 1,
                "uuid": "ERT-123",
                "username": "username123",
                "first_name": "John",
                "last_name": "Johnson",
                "status_id": self.status_active.get_id()
            },
            {
                "id": 2,
                "uuid": "ERT-456",
                "username": "username456",
                "first_name": "Jim",
                "last_name": "Jameson",
                "status_id": self.status_active.get_id()
            }
        ]
        result = Result(True, "", users)
        result.set_full_count(2)
        self.postgres_conn_manager.paginate = MagicMock(return_value=result)
        result_fetch = self.user_manager.search(
            username="username",
            first_name="J",
            last_name="J",
            statuses=[self.status_active.get_id()],
            sorts={"username": 1},
            offset=0,
            limit=2
        )
        user_objs = result_fetch.get_data()
        self.assertEqual(len(users), len(user_objs))
        self.assertEqual(2, result_fetch.get_full_count())
        for i in range(0, len(user_objs)):
            user: User = user_objs[i]
            self.assertEqual(users[i]["id"], user.get_id())
            self.assertEqual(users[i]["uuid"], user.get_uuid())
            self.assertEqual(users[i]["username"], user.get_username())
            self.assertEqual(users[i]["first_name"], user.get_first_name())
            self.assertEqual(users[i]["last_name"], user.get_last_name())
            self.assertEqual(users[i]["status_id"], user.get_status().get_id())

    def test_search_fails_on_search_error(self):
        self.postgres_conn_manager.paginate = MagicMock(return_value=Result(False))
        with self.assertRaises(UserSearchError):
            self.user_manager.search()
            self.fail("Did not fail on search")
        self.postgres_conn_manager.paginate.assert_called_once()

import unittest
from unittest.mock import patch, MagicMock
from modules.user.data.user_data import UserData
from modules.user.exceptions.user.user_create_error import UserCreateError
from modules.user.exceptions.user.user_search_error import UserSearchError
from modules.user.exceptions.user_ticket_name_error import UserTicketNameError
from modules.user.exceptions.user_ticket_password_error import UserTicketPasswordError
from modules.user.exceptions.user_ticket_username_error import UserTicketUsernameError
from modules.user.exceptions.user.user_update_error import UserUpdateError
from modules.user.managers.user_manager import UserManager
from modules.user.managers.user_status_manager import UserStatusManager
from modules.user.objects.tickets.name_ticket import NameTicket
from modules.user.objects.tickets.password_ticket import PasswordTicket
from modules.user.objects.user import User
from modules.user.objects.user_status import UserStatus
from modules.user.objects.tickets.username_ticket import UsernameTicket
from modules.util.managers.postgres_conn_manager import PostgresConnManager
from modules.util.objects.data_list import DataList
from modules.util.objects.result import Result


class UserManagerTest(unittest.TestCase):
    @classmethod
    @patch("modules.util.managers.postgres_conn_manager.PostgresConnManager")
    @patch("modules.user.managers.user_status_manager.UserStatusManager")
    def setUpClass(cls, postgres_conn_manager, user_status_manager) -> None:
        cls.postgres_conn_manager: PostgresConnManager = postgres_conn_manager
        cls.user_status_manager: UserStatusManager = user_status_manager

        cls.status_active = UserStatus(1, "ACTIVE", "Active status")
        cls.status_disabled = UserStatus(2, "DISABLED", "Disabled status")
        cls.user_status_manager.get_all = MagicMock(return_value=DataList(
            "USER_STATUSES",
            [cls.status_active, cls.status_disabled],
            "id",
            "const"
        ))

        cls.user_manager: UserManager = UserManager(
            user_data=UserData(
                postgres_conn_manager=cls.postgres_conn_manager
            ),
            user_status_manager=cls.user_status_manager
        )

    def test_create_creates_user(self):
        username = "username"
        password = "<PASSWORD>"
        first_name = "John"
        last_name = "Johnson"
        data = {
            "id": 1,
            "uuid": "ERT-123",
            "username": username,
            "first_name": first_name,
            "last_name": last_name,
            "status_id": self.status_active.get_id()
        }
        self.postgres_conn_manager.insert = MagicMock(return_value=Result(True))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True, "", [data]))
        user = self.user_manager.create(
            UsernameTicket(username),
            PasswordTicket(password),
            NameTicket(first_name, last_name)
        )
        self.postgres_conn_manager.insert.assert_called_once()
        self.postgres_conn_manager.select.assert_called_once()
        self.assertEqual(data["id"], user.get_id())
        self.assertEqual(data["uuid"], user.get_uuid())
        self.assertEqual(data["username"], user.get_username())
        self.assertEqual(data["first_name"], user.get_first_name())
        self.assertEqual(data["last_name"], user.get_last_name())
        self.assertEqual(data["status_id"], user.get_status().get_id())

    def test_user_ticket_fails_on_bad_username(self):
        username_good_1 = "username"
        username_good_2 = "username123"
        username_good_3 = "123username"
        username_bad_1 = "123"
        username_bad_2 = "UserName123"
        username_bad_3 = "username#123"

        try:
            UsernameTicket(username_good_1)
            UsernameTicket(username_good_2)
            UsernameTicket(username_good_3)
        except Exception as e:
            self.fail(str(e))

        with self.assertRaises(UserTicketUsernameError):
            UsernameTicket(username_bad_1)
            self.fail("Did not fail on bad 1")

        with self.assertRaises(UserTicketUsernameError):
            UsernameTicket(username_bad_2)
            self.fail("Did not fail on bad 2")

        with self.assertRaises(UserTicketUsernameError):
            UsernameTicket(username_bad_3)
            self.fail("Did not fail on bad 3")

    def test_user_ticket_fails_on_bad_password(self):
        password_good = "<PASSWORD>"
        password_bad_1 = "<PASSWORD>"
        password_bad_2 = "Password#"
        password_bad_3 = "<PASSWORD>"

        try:
            PasswordTicket(password_good)
        except Exception as e:
            self.fail(str(e))

        with self.assertRaises(UserTicketPasswordError):
            PasswordTicket(password_bad_1)
            self.fail("Did not fail on bad 1")

        with self.assertRaises(UserTicketPasswordError):
            PasswordTicket(password_bad_2)
            self.fail("Did not fail on bad 2")

        with self.assertRaises(UserTicketPasswordError):
            PasswordTicket(password_bad_3)
            self.fail("Did not fail on bad 3")

    def test_user_ticket_fails_on_bad_name(self):
        with self.assertRaises(UserTicketNameError):
            NameTicket("", "Johnson")
            self.fail("Did not fail on first name empty")

        with self.assertRaises(UserTicketNameError):
            NameTicket("John", "")
            self.fail("Did not fail on last name empty")

    def test_create_fails_on_create_error(self):
        self.postgres_conn_manager.insert = MagicMock(return_value=Result(False))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True))
        with self.assertRaises(UserCreateError):
            self.user_manager.create(
                UsernameTicket("username123"),
                PasswordTicket("<PASSWORD>"),
                NameTicket("John", "Johnson")
            )
            self.fail("Did not fail")
        self.postgres_conn_manager.insert.assert_called_once()
        self.postgres_conn_manager.select.assert_not_called()

    def test_get_fails_on_search_error(self):
        self.postgres_conn_manager.select = MagicMock(return_value=Result(False))
        with self.assertRaises(UserSearchError):
            self.user_manager.get(1)
            self.fail("Did not fail")
        self.postgres_conn_manager.select.assert_called_once()

    def test_update_updates_user(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(True))
        user_id = 1
        first_name = "John"
        last_name = "Johnson"
        data = {
            "id": user_id,
            "uuid": "ERT-123",
            "username": "username123",
            "first_name": first_name,
            "last_name": last_name,
            "status_id": self.status_active.get_id()
        }
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True, "", [data]))
        try:
            user = self.user_manager.update(user_id, NameTicket("John", "Johnson"))
            self.postgres_conn_manager.query.assert_called_once()
            self.postgres_conn_manager.select.assert_called_once()
            self.assertEqual(data["first_name"], user.get_first_name())
            self.assertEqual(data["last_name"], user.get_last_name())
            self.assertEqual(data["id"], user.get_id())
        except Exception as e:
            self.fail(str(e))

    def test_update_fails_on_update(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(False))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True))
        with self.assertRaises(UserUpdateError):
            self.user_manager.update(1, NameTicket("John", "Johnson"))
            self.fail("Did not fail")
        self.postgres_conn_manager.query.assert_called_once()
        self.postgres_conn_manager.select.assert_not_called()

    def test_update_password_updates_password(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(True))
        try:
            self.user_manager.update_password(1, PasswordTicket("<PASSWORD>"))
            self.postgres_conn_manager.query.assert_called_once()
        except Exception as e:
            self.fail(str(e))

    def test_update_password_fails_on_update(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(False))
        with self.assertRaises(UserUpdateError):
            self.user_manager.update_password(1, PasswordTicket("<PASSWORD>"))
            self.fail("Did not fail")
        self.postgres_conn_manager.query.assert_called_once()

    def test_update_status_updates_status(self):
        status_id = self.status_disabled.get_id()
        user_id = 1
        self.postgres_conn_manager.query = MagicMock(return_value=Result(True))
        data = {
            "id": 1,
            "uuid": "ERT-123",
            "username": "username123",
            "first_name": "John",
            "last_name": "Johnson",
            "status_id": status_id
        }
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True, "", [data]))
        try:
            user = self.user_manager.update_status(user_id, status_id)
            self.postgres_conn_manager.query.assert_called_once()
            self.postgres_conn_manager.select.assert_called_once()
            self.assertEqual(data["status_id"], user.get_status().get_id())
            self.assertEqual(data["id"], user.get_id())
        except Exception as e:
            self.fail(str(e))

    def test_update_status_fails_on_update_error(self):
        self.postgres_conn_manager.query = MagicMock(return_value=Result(False))
        self.postgres_conn_manager.select = MagicMock(return_value=Result(True))
        with self.assertRaises(UserUpdateError):
            self.user_manager.update_status(1, self.status_disabled.get_id())
            self.fail("Did not fail")
        self.postgres_conn_manager.query.assert_called_once()
        self.postgres_conn_manager.select.assert_not_called()

    def test_search_searches_users(self):
        users = [
            {
                "id": 1,
                "uuid": "ERT-123",
                "username": "username123",
                "first_name": "John",
                "last_name": "Johnson",
                "status_id": self.status_active.get_id()
            },
            {
                "id": 2,
                "uuid": "ERT-456",
                "username": "username456",
                "first_name": "Jim",
                "last_name": "Jameson",
                "status_id": self.status_active.get_id()
            }
        ]
        result = Result(True, "", users)
        result.set_full_count(2)
        self.postgres_conn_manager.paginate = MagicMock(return_value=result)
        result_fetch = self.user_manager.search(
            username="username",
            first_name="J",
            last_name="J",
            statuses=[self.status_active.get_id()],
            sorts={"username": 1},
            offset=0,
            limit=2
        )
        user_objs = result_fetch.get_data()
        self.assertEqual(len(users), len(user_objs))
        self.assertEqual(2, result_fetch.get_full_count())
        for i in range(0, len(user_objs)):
            user: User = user_objs[i]
            self.assertEqual(users[i]["id"], user.get_id())
            self.assertEqual(users[i]["uuid"], user.get_uuid())
            self.assertEqual(users[i]["username"], user.get_username())
            self.assertEqual(users[i]["first_name"], user.get_first_name())
            self.assertEqual(users[i]["last_name"], user.get_last_name())
            self.assertEqual(users[i]["status_id"], user.get_status().get_id())

    def test_search_fails_on_search_error(self):
        self.postgres_conn_manager.paginate = MagicMock(return_value=Result(False))
        with self.assertRaises(UserSearchError):
            self.user_manager.search()
            self.fail("Did not fail on search")
        self.postgres_conn_manager.paginate.assert_called_once()
