import pytest
from unittest.mock import MagicMock, patch
from uuid import uuid4

from sqlalchemy.orm import Session

from preloop.models.crud.account import CRUDAccount
from preloop.models.models.account import Account


@pytest.fixture
def mock_db_session():
    """Fixture for a mock database session."""
    session = MagicMock(spec=Session)
    return session


@pytest.fixture
def crud_account():
    """Fixture for a CRUDAccount instance."""
    return CRUDAccount(Account)


def test_create(crud_account, mock_db_session):
    """Test creating an account."""
    # Arrange
    obj_in = {"email": "test@example.com", "username": "testuser"}

    # Act
    with patch("preloop.models.crud.base.CRUDBase.create") as mock_create:
        crud_account.create(mock_db_session, obj_in=obj_in)

        # Assert
        mock_create.assert_called_once()
        created_data = mock_create.call_args[1]["obj_in"]
        assert "created" in created_data
        assert "last_updated" in created_data


def test_update(crud_account, mock_db_session):
    """Test updating an account."""
    # Arrange
    db_obj = Account(id=str(uuid4()))
    obj_in = {"username": "newusername"}

    # Act
    with patch("preloop.models.crud.base.CRUDBase.update") as mock_update:
        crud_account.update(mock_db_session, db_obj=db_obj, obj_in=obj_in)

        # Assert
        mock_update.assert_called_once()
        updated_data = mock_update.call_args[1]["obj_in"]
        assert "last_updated" in updated_data


# NOTE: get_by_email and get_by_username methods have been moved to User CRUD
# These tests are no longer applicable to Account model
#
# def test_get_by_email(crud_account, mock_db_session):
#     """Test retrieving an account by email."""
#     # Arrange
#     email = "test@example.com"
#     mock_account = Account(id=str(uuid4()), email=email)
#
#     mock_query = MagicMock()
#     mock_db_session.query.return_value = mock_query
#     mock_query.filter.return_value = mock_query
#     mock_query.first.return_value = mock_account
#
#     # Act
#     result = crud_account.get_by_email(mock_db_session, email=email)
#
#     # Assert
#     assert result.email == email
#
#
# def test_get_by_username(crud_account, mock_db_session):
#     """Test retrieving an account by username."""
#     # Arrange
#     username = "testuser"
#     mock_account = Account(id=str(uuid4()), username=username)
#
#     mock_query = MagicMock()
#     mock_db_session.query.return_value = mock_query
#     mock_query.filter.return_value = mock_query
#     mock_query.first.return_value = mock_account
#
#     # Act
#     result = crud_account.get_by_username(mock_db_session, username=username)
#
#     # Assert
#     assert result.username == username


def test_get_active(crud_account, mock_db_session):
    """Test retrieving active accounts."""
    # Arrange
    mock_accounts = [Account(id=str(uuid4()), is_active=True)]

    mock_query = MagicMock()
    mock_db_session.query.return_value = mock_query
    mock_query.filter.return_value = mock_query
    mock_query.offset.return_value = mock_query
    mock_query.limit.return_value = mock_query
    mock_query.all.return_value = mock_accounts

    # Act
    result = crud_account.get_active(mock_db_session)

    # Assert
    assert len(result) == 1
    assert result[0].is_active


# NOTE: get_by_oauth method has been moved to User CRUD
# This test is no longer applicable to Account model
#
# def test_get_by_oauth(crud_account, mock_db_session):
#     """Test retrieving an account by OAuth provider and ID."""
#     # Arrange
#     provider = "github"
#     oauth_id = "12345"
#     mock_account = Account(id=str(uuid4()), oauth_provider=provider, oauth_id=oauth_id)
#
#     mock_query = MagicMock()
#     mock_db_session.query.return_value = mock_query
#     mock_query.filter.return_value = mock_query
#     mock_query.first.return_value = mock_account
#
#     # Act
#     result = crud_account.get_by_oauth(
#         mock_db_session, provider=provider, oauth_id=oauth_id
#     )
#
#     # Assert
#     assert result.oauth_provider == provider
#     assert result.oauth_id == oauth_id


def test_add_to_organization(crud_account, mock_db_session):
    """Test adding an account to an organization."""
    # Arrange
    account_id = str(uuid4())
    organization_id = str(uuid4())

    # Act
    crud_account.add_to_organization(
        mock_db_session, account_id=account_id, organization_id=organization_id
    )

    # Assert
    mock_db_session.execute.assert_called_once()
    mock_db_session.commit.assert_called_once()


def test_remove_from_organization(crud_account, mock_db_session):
    """Test removing an account from an organization."""
    # Arrange
    account_id = str(uuid4())
    organization_id = str(uuid4())

    # Act
    crud_account.remove_from_organization(
        mock_db_session, account_id=account_id, organization_id=organization_id
    )

    # Assert
    mock_db_session.execute.assert_called_once()
    mock_db_session.commit.assert_called_once()


def test_update_organization_role(crud_account, mock_db_session):
    """Test updating an account's role in an organization."""
    # Arrange
    account_id = str(uuid4())
    organization_id = str(uuid4())
    role = "admin"

    # Act
    crud_account.update_organization_role(
        mock_db_session,
        account_id=account_id,
        organization_id=organization_id,
        role=role,
    )

    # Assert
    mock_db_session.execute.assert_called_once()
    mock_db_session.commit.assert_called_once()


def test_get_organizations(crud_account, mock_db_session):
    """Test getting all organizations and roles for an account."""
    # Arrange
    account_id = str(uuid4())
    mock_account = Account(id=account_id)
    mock_account.organizations = [
        MagicMock(organization_id="org1", role="admin"),
        MagicMock(organization_id="org2", role="member"),
    ]

    crud_account.get = MagicMock(return_value=mock_account)

    # Act
    result = crud_account.get_organizations(mock_db_session, account_id=account_id)

    # Assert
    assert result == {"org1": "admin", "org2": "member"}


def test_get_organizations_not_found(crud_account, mock_db_session):
    """Test getting organizations when account doesn't exist."""
    # Arrange
    account_id = str(uuid4())
    crud_account.get = MagicMock(return_value=None)

    # Act
    result = crud_account.get_organizations(mock_db_session, account_id=account_id)

    # Assert
    assert result == {}


def test_update_with_pydantic_model(crud_account, mock_db_session):
    """Test updating an account with a Pydantic model."""
    # Arrange
    db_obj = Account(id=str(uuid4()))

    # Create a mock Pydantic model with model_dump method
    mock_pydantic_obj = MagicMock()
    mock_pydantic_obj.model_dump.return_value = {"username": "newusername"}

    # Act
    with patch("preloop.models.crud.base.CRUDBase.update") as mock_update:
        crud_account.update(mock_db_session, db_obj=db_obj, obj_in=mock_pydantic_obj)

        # Assert
        mock_pydantic_obj.model_dump.assert_called_once_with(exclude_unset=True)
        mock_update.assert_called_once()
        updated_data = mock_update.call_args[1]["obj_in"]
        assert "last_updated" in updated_data
