import json
from unittest.mock import patch, MagicMock
from graphene_django.utils.testing import GraphQLTestCase
from .models import Account
import uuid

class BillingGraphQLTests(GraphQLTestCase):
    GRAPHQL_URL = '/graphql/public/'

    @patch('billing_app.schemas.tigerbeetle_client')
    def test_create_transfer_new_accounts(self, mock_tb_client):
        """
        Tests that a transfer is created successfully and that new accounts
        are created lazily in both the local DB and in TigerBeetle.
        """
        # Configure the mock to return no errors
        mock_tb_client.create_accounts.return_value = []
        mock_tb_client.create_transfers.return_value = []

        debit_account_id = str(uuid.uuid4())
        credit_account_id = str(uuid.uuid4())
        amount = 100
        ledger = 1
        code = 200

        # Ensure accounts do not exist locally
        self.assertEqual(Account.objects.count(), 0)

        response = self.query(
            '''
            mutation createTransfer(
                $debitAccountId: String!,
                $creditAccountId: String!,
                $amount: Int!,
                $ledger: Int!,
                $code: Int!
            ) {
                createTransfer(
                    debitAccountId: $debitAccountId,
                    creditAccountId: $creditAccountId,
                    amount: $amount,
                    ledger: $ledger,
                    code: $code
                ) {
                    success
                    message
                    transferId
                }
            }
            ''',
            variables={
                'debitAccountId': debit_account_id,
                'creditAccountId': credit_account_id,
                'amount': amount,
                'ledger': ledger,
                'code': code
            }
        )

        self.assertResponseNoErrors(response)
        content = json.loads(response.content)
        result = content['data']['createTransfer']

        self.assertTrue(result['success'])
        self.assertIsNotNone(result['transferId'])
        self.assertEqual(result['message'], 'Transfer completed successfully.')

        # Verify local accounts were created
        self.assertEqual(Account.objects.count(), 2)
        self.assertTrue(Account.objects.filter(uuid=debit_account_id).exists())
        self.assertTrue(Account.objects.filter(uuid=credit_account_id).exists())
        
        # Verify TigerBeetle client was called correctly
        mock_tb_client.create_accounts.assert_called_once()
        self.assertEqual(len(mock_tb_client.create_accounts.call_args[0][0]), 2) # Called with 2 accounts
        
        mock_tb_client.create_transfers.assert_called_once()
        self.assertEqual(len(mock_tb_client.create_transfers.call_args[0][0]), 1) # Called with 1 transfer
        transfer_arg = mock_tb_client.create_transfers.call_args[0][0][0]
        self.assertEqual(transfer_arg.amount, amount)

    @patch('billing_app.schemas.tigerbeetle_client')
    def test_create_transfer_existing_accounts(self, mock_tb_client):
        """
        Tests that a transfer is created successfully with existing accounts
        and that `create_accounts` is not called.
        """
        mock_tb_client.create_transfers.return_value = []

        debit_account_id = uuid.uuid4()
        credit_account_id = uuid.uuid4()

        # Pre-populate the local database
        Account.objects.create(uuid=debit_account_id)
        Account.objects.create(uuid=credit_account_id)

        self.assertEqual(Account.objects.count(), 2)
        
        response = self.query(
            '''
            mutation createTransfer(
                $debitAccountId: String!,
                $creditAccountId: String!,
                $amount: Int!,
                $ledger: Int!,
                $code: Int!
            ) {
                createTransfer(
                    debitAccountId: $debitAccountId,
                    creditAccountId: $creditAccountId,
                    amount: $amount,
                    ledger: $ledger,
                    code: $code
                ) {
                    success
                    message
                    transferId
                }
            }
            ''',
            variables={
                'debitAccountId': str(debit_account_id),
                'creditAccountId': str(credit_account_id),
                'amount': 50,
                'ledger': 1,
                'code': 201
            }
        )

        self.assertResponseNoErrors(response)
        
        # Verify that create_accounts was NOT called
        mock_tb_client.create_accounts.assert_not_called()
        
        # Verify that create_transfers WAS called
        mock_tb_client.create_transfers.assert_called_once()

    def test_create_transfer_invalid_uuid(self):
        """
        Tests that the mutation fails gracefully with an invalid UUID.
        """
        response = self.query(
            '''
            mutation createTransfer(
                $debitAccountId: String!,
                $creditAccountId: String!,
                $amount: Int!,
                $ledger: Int!,
                $code: Int!
            ) {
                createTransfer(
                    debitAccountId: $debitAccountId,
                    creditAccountId: $creditAccountId,
                    amount: $amount,
                    ledger: $ledger,
                    code: $code
                ) {
                    success
                    message
                    transferId
                }
            }
            ''',
            variables={
                'debitAccountId': 'not-a-uuid',
                'creditAccountId': str(uuid.uuid4()),
                'amount': 50,
                'ledger': 1,
                'code': 202
            }
        )

        self.assertResponseNoErrors(response)
        content = json.loads(response.content)
        result = content['data']['createTransfer']

        self.assertFalse(result['success'])
        self.assertEqual(result['message'], 'Invalid account ID format. Must be a valid UUID.')

