import unittest
from http import HTTPStatus
from django.test import TestCase, override_settings
from django.contrib.admin.sites import AdminSite
from django.test import RequestFactory, Client
from django.contrib.auth.models import User, Permission
from django.utils import timezone
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.messages.storage.fallback import FallbackStorage
from django.contrib.messages import get_messages
from django.utils.translation import gettext_lazy as _
from django.urls import reverse, reverse_lazy
from django.http import HttpResponseRedirect, HttpResponse
from unittest.mock import Mock, patch
from django.test.utils import override_settings
from django.urls import path, include
from django.contrib import admin as django_admin

from members.tests.utils import create_custom_user
from members.models import Member, MALE, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE
from ..models import (
    Ledger, Statement, StatementUnSubmitted, StatementConfirmed, Transaction, Bill,
    StatementSubmitted
)
from ..admin import (
    LedgerAdmin, StatementUnSubmittedAdmin, StatementSubmittedAdmin,
    StatementConfirmedAdmin, TransactionAdmin, BillAdmin
)


class AdminTestCase(TestCase):
    def setUp(self, model, admin):
        self.factory = RequestFactory()
        self.model = model
        if model is not None and admin is not None:
            self.admin = admin(model, AdminSite())
        superuser = User.objects.create_superuser(
            username='superuser', password='secret'
        )
        standard = create_custom_user('standard', ['Standard'], 'Paul', 'Wulter')
        trainer = create_custom_user('trainer', ['Standard', 'Trainings'], 'Lise', 'Lotte')
        treasurer = create_custom_user('treasurer', ['Standard', 'Finance'], 'Lara', 'Litte')
        materialwarden = create_custom_user('materialwarden', ['Standard', 'Material'], 'Loro', 'Lutte')

    def _login(self, name):
        c = Client()
        res = c.login(username=name, password='secret')
        # make sure we logged in
        assert res
        return c


class StatementUnSubmittedAdminTestCase(AdminTestCase):
    """Test cases for StatementUnSubmittedAdmin"""

    def setUp(self):
        super().setUp(model=StatementUnSubmitted, admin=StatementUnSubmittedAdmin)

        self.superuser = User.objects.get(username='superuser')
        self.member = Member.objects.create(
            prename="Test", lastname="User", birth_date=timezone.now().date(),
            email="test@example.com", gender=MALE, user=self.superuser
        )

        self.statement = StatementUnSubmitted.objects.create(
            short_description='Test Statement',
            explanation='Test explanation',
            night_cost=25
        )

        # Create excursion for testing
        self.excursion = Freizeit.objects.create(
            name='Test Excursion',
            kilometers_traveled=100,
            tour_type=GEMEINSCHAFTS_TOUR,
            tour_approach=MUSKELKRAFT_ANREISE,
            difficulty=1
        )

        # Create confirmed statement with excursion
        self.statement_with_excursion = StatementUnSubmitted.objects.create(
            short_description='With Excursion',
            explanation='Test explanation',
            night_cost=25,
            excursion=self.excursion,
        )

    def test_save_model_with_member(self):
        """Test save_model sets created_by for new objects"""
        request = self.factory.post('/')
        request.user = self.superuser

        # Test with change=False (new object)
        new_statement = Statement(short_description='New Statement')
        self.admin.save_model(request, new_statement, None, change=False)
        self.assertEqual(new_statement.created_by, self.member)

    def test_get_readonly_fields_submitted(self):
        """Test readonly fields when statement is submitted"""
        # Mark statement as submitted
        self.statement.status = Statement.SUBMITTED
        readonly_fields = self.admin.get_readonly_fields(None, self.statement)
        self.assertIn('status', readonly_fields)
        self.assertIn('excursion', readonly_fields)
        self.assertIn('short_description', readonly_fields)

    def test_get_readonly_fields_not_submitted(self):
        """Test readonly fields when statement is not submitted"""
        readonly_fields = self.admin.get_readonly_fields(None, self.statement)
        self.assertEqual(readonly_fields, ['status', 'excursion'])

    def test_submit_view_insufficient_permission(self):
        url = reverse('admin:finance_statementunsubmitted_submit',
                      args=(self.statement.pk,))
        c = self._login('standard')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertContains(response, _('Insufficient permissions.'))

    def test_submit_view_get(self):
        url = reverse('admin:finance_statementunsubmitted_submit',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertContains(response, _('Submit statement'))

    def test_submit_view_get_with_excursion(self):
        url = reverse('admin:finance_statementunsubmitted_submit',
                      args=(self.statement_with_excursion.pk,))
        c = self._login('superuser')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertContains(response, _('Finance overview'))

    def test_submit_view_post(self):
        url = reverse('admin:finance_statementunsubmitted_submit',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'apply': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        text = _("Successfully submited %(name)s. The finance department will notify the requestors as soon as possible.") % {'name': str(self.statement)}
        self.assertContains(response, text)


class StatementSubmittedAdminTestCase(AdminTestCase):
    """Test cases for StatementSubmittedAdmin"""

    def setUp(self):
        super().setUp(model=StatementSubmitted, admin=StatementSubmittedAdmin)

        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.member = Member.objects.create(
            prename="Test", lastname="User", birth_date=timezone.now().date(),
            email="test@example.com", gender=MALE, user=self.user
        )

        self.finance_user = User.objects.create_user('finance', 'finance@example.com', 'pass')
        finance_perm = Permission.objects.get(codename='process_statementsubmitted')
        self.finance_user.user_permissions.add(finance_perm)

        self.statement = Statement.objects.create(
            short_description='Submitted Statement',
            explanation='Test explanation',
            status=Statement.SUBMITTED,
            submitted_by=self.member,
            submitted_date=timezone.now(),
            night_cost=25
        )
        self.statement_unsubmitted = StatementUnSubmitted.objects.create(
            short_description='Submitted Statement',
            explanation='Test explanation',
            night_cost=25
        )
        self.transaction = Transaction.objects.create(
            reference='verylonglong' * 14,
            amount=3,
            statement=self.statement,
            member=self.member,
        )

        # Create commonly used test objects
        self.ledger = Ledger.objects.create(name='Test Ledger')
        self.excursion = Freizeit.objects.create(
            name='Test Excursion',
            kilometers_traveled=100,
            tour_type=GEMEINSCHAFTS_TOUR,
            tour_approach=MUSKELKRAFT_ANREISE,
            difficulty=1
        )
        self.other_member = Member.objects.create(
            prename="Other", lastname="Member", birth_date=timezone.now().date(),
            email="other@example.com", gender=MALE
        )

        # Create statements for generate transactions tests
        self.statement_no_trans_success = Statement.objects.create(
            short_description='No Transactions Success',
            explanation='Test explanation',
            status=Statement.SUBMITTED,
            submitted_by=self.member,
            submitted_date=timezone.now(),
            night_cost=25
        )
        self.statement_no_trans_error = Statement.objects.create(
            short_description='No Transactions Error',
            explanation='Test explanation',
            status=Statement.SUBMITTED,
            submitted_by=self.member,
            submitted_date=timezone.now(),
            night_cost=25
        )

        # Create bills for generate transactions tests
        self.bill_for_success = Bill.objects.create(
            statement=self.statement_no_trans_success,
            short_description='Test Bill Success',
            amount=50,
            paid_by=self.member,
            costs_covered=True
        )
        self.bill_for_error = Bill.objects.create(
            statement=self.statement_no_trans_error,
            short_description='Test Bill Error',
            amount=50,
            paid_by=None,  # No payer will cause generate_transactions to fail
            costs_covered=True,
        )

    def _create_matching_bill(self, statement=None, amount=None):
        """Helper method to create a bill that matches transaction amount"""
        return Bill.objects.create(
            statement=statement or self.statement,
            short_description='Test Bill',
            amount=amount or self.transaction.amount,
            paid_by=self.member,
            costs_covered=True
        )

    def _create_non_matching_bill(self, statement=None, amount=100):
        """Helper method to create a bill that doesn't match transaction amount"""
        return Bill.objects.create(
            statement=statement or self.statement,
            short_description='Non-matching Bill',
            amount=amount,
            paid_by=self.member
        )

    def test_has_add_permission(self):
        """Test that add permission is disabled"""
        request = self.factory.get('/')
        request.user = self.finance_user
        self.assertFalse(self.admin.has_add_permission(request))

    def test_has_change_permission_with_permission(self):
        """Test change permission with proper permission"""
        request = self.factory.get('/')
        request.user = self.finance_user
        self.assertTrue(self.admin.has_change_permission(request))

    def test_has_change_permission_without_permission(self):
        """Test change permission without proper permission"""
        request = self.factory.get('/')
        request.user = self.user
        self.assertFalse(self.admin.has_change_permission(request))

    def test_has_delete_permission(self):
        """Test that delete permission is disabled"""
        request = self.factory.get('/')
        request.user = self.finance_user
        self.assertFalse(self.admin.has_delete_permission(request))

    def test_readonly_fields(self):
        self.assertNotIn('explanation',
                         self.admin.get_readonly_fields(None, self.statement_unsubmitted))

    def test_change(self):
        url = reverse('admin:finance_statementsubmitted_change',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.get(url)
        self.assertEqual(response.status_code, HTTPStatus.OK)

    def test_overview_view(self):
        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.get(url)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertContains(response, _('View submitted statement'))

    def test_overview_view_statement_not_found(self):
        """Test overview_view with statement that can't be found in StatementSubmitted queryset"""
        # When trying to access an unsubmitted statement via StatementSubmitted admin,
        # the decorator will fail to find it and show "Statement not found"
        self.statement.status = Statement.UNSUBMITTED
        self.statement.save()

        url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        messages = list(get_messages(response.wsgi_request))
        expected_text = str(_("Statement not found."))
        self.assertTrue(any(expected_text in str(msg) for msg in messages))

    def test_overview_view_transaction_execution_confirm(self):
        """Test overview_view transaction execution confirm"""
        # Set up statement to be valid for confirmation
        self.transaction.ledger = self.ledger
        self.transaction.save()

        # Create a bill that matches the transaction amount to make it valid
        self._create_matching_bill()

        url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'transaction_execution_confirm': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        success_text = _("Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again.") % {'name': str(self.statement)}
        self.assertContains(response, success_text)
        self.statement.refresh_from_db()
        self.assertTrue(self.statement.confirmed)

    def test_overview_view_transaction_execution_confirm_and_send(self):
        """Test overview_view transaction execution confirm and send"""
        # Set up statement to be valid for confirmation
        self.transaction.ledger = self.ledger
        self.transaction.save()

        # Create a bill that matches the transaction amount to make it valid
        self._create_matching_bill()

        url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'transaction_execution_confirm_and_send': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        success_text = _("Successfully sent receipt to the office.")
        self.assertContains(response, success_text)

    def test_overview_view_confirm_valid(self):
        """Test overview_view confirm with valid statement"""
        # Create a statement with valid configuration
        # Set up transaction with ledger to make it valid
        self.transaction.ledger = self.ledger
        self.transaction.save()

        # Create a bill that matches the transaction amount to make total valid
        self._create_matching_bill()

        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, data={'confirm': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertContains(response, _('Statement confirmed'))

    def test_overview_view_confirm_non_matching_transactions(self):
        """Test overview_view confirm with non-matching transactions"""
        # Create a bill that doesn't match the transaction
        self._create_non_matching_bill()

        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'confirm': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        error_text = _("Transactions do not match the covered expenses. Please correct the mistakes listed below.")
        self.assertContains(response, error_text)

    def test_overview_view_confirm_missing_ledger(self):
        """Test overview_view confirm with missing ledger"""
        # Ensure transaction has no ledger (ledger=None)
        self.transaction.ledger = None
        self.transaction.save()

        # Create a bill that matches the transaction amount to pass the first check
        self._create_matching_bill()

        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'confirm': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        # Check the Django messages for the error
        messages = list(get_messages(response.wsgi_request))
        expected_text = str(_("Some transactions have no ledger configured. Please fill in the gaps."))
        self.assertTrue(any(expected_text in str(msg) for msg in messages))

    def test_overview_view_confirm_invalid_allowance_to(self):
        """Test overview_view confirm with invalid allowance"""
        # Create excursion and set up invalid allowance configuration
        self.statement.excursion = self.excursion
        self.statement.save()

        # Add allowance recipient who is not a youth leader for this excursion
        self.statement_no_trans_success.allowance_to.add(self.other_member)

        # Generate required transactions
        self.statement_no_trans_success.generate_transactions()
        for trans in self.statement_no_trans_success.transaction_set.all():
            trans.ledger = self.ledger
            trans.save()

        # Check validity obstruction is allowances
        self.assertEqual(self.statement_no_trans_success.validity, Statement.INVALID_ALLOWANCE_TO)

        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement_no_trans_success.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'confirm': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        # Check the Django messages for the error
        messages = list(get_messages(response.wsgi_request))
        expected_text = str(_("The configured recipients for the allowance don't match the regulations. Please correct this on the excursion."))
        self.assertTrue(any(expected_text in str(msg) for msg in messages))

    def test_overview_view_reject(self):
        """Test overview_view reject statement"""
        url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'reject': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        success_text = _("Successfully rejected %(name)s. The requestor can reapply, when needed.") %\
            {'name': str(self.statement)}
        self.assertContains(response, success_text)

        # Verify statement was rejected
        self.statement.refresh_from_db()
        self.assertFalse(self.statement.submitted)

    def test_overview_view_generate_transactions_existing(self):
        """Test overview_view generate transactions with existing transactions"""
        # Ensure there's already a transaction
        self.assertTrue(self.statement.transaction_set.count() > 0)

        url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'generate_transactions': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        error_text = _("%(name)s already has transactions. Please delete them first, if you want to generate new ones") % {'name': str(self.statement)}
        self.assertContains(response, error_text)

    def test_overview_view_generate_transactions_success(self):
        """Test overview_view generate transactions successfully"""
        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement_no_trans_success.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'generate_transactions': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        success_text = _("Successfully generated transactions for %(name)s") %\
            {'name': str(self.statement_no_trans_success)}
        self.assertContains(response, success_text)

    def test_overview_view_generate_transactions_error(self):
        """Test overview_view generate transactions with error"""
        url = reverse('admin:finance_statementsubmitted_overview',
                      args=(self.statement_no_trans_error.pk,))
        c = self._login('superuser')
        response = c.post(url, follow=True, data={'generate_transactions': ''})
        self.assertEqual(response.status_code, HTTPStatus.OK)
        messages = list(get_messages(response.wsgi_request))
        expected_text = str(_("Error while generating transactions for %(name)s. Do all bills have a payer and, if this statement is attached to an excursion, was a person selected that receives the subsidies?") %\
            {'name': str(self.statement_no_trans_error)})
        self.assertTrue(any(expected_text in str(msg) for msg in messages))

    def test_reduce_transactions_view(self):
        url = reverse('admin:finance_statementsubmitted_reduce_transactions',
                      args=(self.statement.pk,))
        c = self._login('superuser')
        response = c.get(url, data={'redirectTo': reverse('admin:finance_statementsubmitted_changelist')},
                         follow=True)
        self.assertContains(response,
                            _("Successfully reduced transactions for %(name)s.") %\
                            {'name': str(self.statement)})


class StatementConfirmedAdminTestCase(AdminTestCase):
    """Test cases for StatementConfirmedAdmin"""

    def setUp(self):
        super().setUp(model=StatementConfirmed, admin=StatementConfirmedAdmin)

        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.member = Member.objects.create(
            prename="Test", lastname="User", birth_date=timezone.now().date(),
            email="test@example.com", gender=MALE, user=self.user
        )

        self.finance_user = User.objects.create_user('finance', 'finance@example.com', 'pass')
        unconfirm_perm = Permission.objects.get(codename='may_manage_confirmed_statements')
        self.finance_user.user_permissions.add(unconfirm_perm)

        # Create a base statement first
        base_statement = Statement.objects.create(
            short_description='Confirmed Statement',
            explanation='Test explanation',
            status=Statement.CONFIRMED,
            confirmed_by=self.member,
            confirmed_date=timezone.now(),
            night_cost=25
        )

        # StatementConfirmed is a proxy model, so we can get it from the base statement
        self.statement = StatementConfirmed.objects.get(pk=base_statement.pk)

        # Create an unconfirmed statement for testing
        self.unconfirmed_statement = Statement.objects.create(
            short_description='Unconfirmed Statement',
            explanation='Test explanation',
            status=Statement.SUBMITTED,
            night_cost=25
        )

        # Create excursion for testing
        self.excursion = Freizeit.objects.create(
            name='Test Excursion',
            kilometers_traveled=100,
            tour_type=GEMEINSCHAFTS_TOUR,
            tour_approach=MUSKELKRAFT_ANREISE,
            difficulty=1
        )

        # Create confirmed statement with excursion
        confirmed_with_excursion_base = Statement.objects.create(
            short_description='Confirmed with Excursion',
            explanation='Test explanation',
            status=Statement.CONFIRMED,
            confirmed_by=self.member,
            confirmed_date=timezone.now(),
            excursion=self.excursion,
            night_cost=25
        )
        self.statement_with_excursion = StatementConfirmed.objects.get(pk=confirmed_with_excursion_base.pk)

    def _add_session_to_request(self, request):
        """Add session to request"""
        middleware = SessionMiddleware(lambda req: None)
        middleware.process_request(request)
        request.session.save()

        middleware = MessageMiddleware(lambda req: None)
        middleware.process_request(request)
        request._messages = FallbackStorage(request)

    def test_has_add_permission(self):
        """Test that add permission is disabled"""
        request = self.factory.get('/')
        request.user = self.finance_user
        self.assertFalse(self.admin.has_add_permission(request))

    def test_has_change_permission(self):
        """Test that change permission is disabled"""
        request = self.factory.get('/')
        request.user = self.finance_user
        self.assertFalse(self.admin.has_change_permission(request))

    def test_has_delete_permission(self):
        """Test that delete permission is disabled"""
        request = self.factory.get('/')
        request.user = self.finance_user
        self.assertFalse(self.admin.has_delete_permission(request))

    def test_unconfirm_view_not_confirmed_statement(self):
        """Test unconfirm_view with statement that is not confirmed"""
        # Create request for unconfirmed statement
        request = self.factory.get('/')
        request.user = self.finance_user
        self._add_session_to_request(request)

        # Test with unconfirmed statement (should trigger error path)
        self.assertFalse(self.unconfirmed_statement.confirmed)

        # Call unconfirm_view - this should go through error path
        response = self.admin.unconfirm_view(request, self.unconfirmed_statement.pk)

        # Should redirect due to not confirmed error
        self.assertEqual(response.status_code, 302)

    def test_unconfirm_view_post_unconfirm_action(self):
        """Test unconfirm_view POST request with 'unconfirm' action"""
        # Create POST request with unconfirm action
        request = self.factory.post('/', {'unconfirm': 'true'})
        request.user = self.finance_user
        self._add_session_to_request(request)

        # Ensure statement is confirmed
        self.assertTrue(self.statement.confirmed)
        self.assertIsNotNone(self.statement.confirmed_by)
        self.assertIsNotNone(self.statement.confirmed_date)

        # Call unconfirm_view - this should execute the unconfirm action
        response = self.admin.unconfirm_view(request, self.statement.pk)

        # Should redirect after successful unconfirm
        self.assertEqual(response.status_code, 302)

        # Verify statement was unconfirmed (need to reload from DB)
        self.statement.refresh_from_db()
        self.assertFalse(self.statement.confirmed)
        self.assertIsNone(self.statement.confirmed_date)

    def test_unconfirm_view_get_render_template(self):
        """Test unconfirm_view GET request rendering template"""
        # Create GET request (no POST data)
        request = self.factory.get('/')
        request.user = self.finance_user
        self._add_session_to_request(request)

        # Ensure statement is confirmed
        self.assertTrue(self.statement.confirmed)

        # Call unconfirm_view
        response = self.admin.unconfirm_view(request, self.statement.pk)

        # Should render template (status 200)
        self.assertEqual(response.status_code, 200)

        # Check response content contains expected template elements
        self.assertIn(str(_('Unconfirm statement')).encode('utf-8'), response.content)
        self.assertIn(self.statement.short_description.encode(), response.content)

    def test_statement_summary_view_insufficient_permission(self):
        url = reverse('admin:finance_statementconfirmed_summary',
                      args=(self.statement_with_excursion.pk,))
        c = self._login('standard')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)

    def test_statement_summary_view_unconfirmed(self):
        url = reverse('admin:finance_statementconfirmed_summary',
                      args=(self.unconfirmed_statement.pk,))
        c = self._login('superuser')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertContains(response, _('Statement not found.'))

    def test_statement_summary_view_confirmed_with_excursion(self):
        """Test statement_summary_view when statement is confirmed with excursion"""
        url = reverse('admin:finance_statementconfirmed_summary',
                      args=(self.statement_with_excursion.pk,))
        c = self._login('superuser')
        response = c.get(url, follow=True)
        self.assertEqual(response.status_code, HTTPStatus.OK)
        self.assertEqual(response.headers['Content-Type'], 'application/pdf')


class TransactionAdminTestCase(TestCase):
    """Test cases for TransactionAdmin"""

    def setUp(self):
        self.site = AdminSite()
        self.factory = RequestFactory()
        self.admin = TransactionAdmin(Transaction, self.site)

        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.member = Member.objects.create(
            prename="Test", lastname="User", birth_date=timezone.now().date(),
            email="test@example.com", gender=MALE, user=self.user
        )

        self.ledger = Ledger.objects.create(name='Test Ledger')
        self.statement = Statement.objects.create(
            short_description='Test Statement',
            explanation='Test explanation'
        )

        self.transaction = Transaction.objects.create(
            member=self.member,
            ledger=self.ledger,
            amount=100,
            reference='Test transaction',
            statement=self.statement
        )

    def test_has_add_permission(self):
        """Test that add permission is disabled"""
        request = self.factory.get('/')
        request.user = self.user
        self.assertFalse(self.admin.has_add_permission(request))

    def test_has_change_permission(self):
        """Test that change permission is disabled"""
        request = self.factory.get('/')
        request.user = self.user
        self.assertFalse(self.admin.has_change_permission(request))

    def test_has_delete_permission(self):
        """Test that delete permission is disabled"""
        request = self.factory.get('/')
        request.user = self.user
        self.assertFalse(self.admin.has_delete_permission(request))

    def test_get_readonly_fields_confirmed(self):
        """Test readonly fields when transaction is confirmed"""
        self.transaction.confirmed = True
        readonly_fields = self.admin.get_readonly_fields(None, self.transaction)
        self.assertEqual(readonly_fields, self.admin.fields)

    def test_get_readonly_fields_not_confirmed(self):
        """Test readonly fields when transaction is not confirmed"""
        readonly_fields = self.admin.get_readonly_fields(None, self.transaction)
        self.assertEqual(readonly_fields, ())
