"""Tests for Payment model"""

import pytest
from datetime import datetime, date, timedelta
from decimal import Decimal
from app import db, create_app
from sqlalchemy.pool import StaticPool
from app.models import Payment, Invoice, User, Project, Client
from factories import UserFactory, ClientFactory, ProjectFactory, InvoiceFactory, PaymentFactory


@pytest.fixture
def app():
    """Isolated app for payment model tests using in-memory SQLite to avoid file locks on Windows."""
    app = create_app(
        {
            "TESTING": True,
            "SQLALCHEMY_DATABASE_URI": "sqlite://",
            "WTF_CSRF_ENABLED": False,
            "SQLALCHEMY_ENGINE_OPTIONS": {
                "connect_args": {"check_same_thread": False, "timeout": 30},
                "poolclass": StaticPool,
            },
            "SQLALCHEMY_SESSION_OPTIONS": {"expire_on_commit": False},
        }
    )
    with app.app_context():
        db.create_all()
        try:
            # Improve SQLite concurrency behavior
            db.session.execute("PRAGMA journal_mode=WAL;")
            db.session.execute("PRAGMA synchronous=NORMAL;")
            db.session.execute("PRAGMA busy_timeout=30000;")
            db.session.commit()
        except Exception:
            db.session.rollback()
        try:
            yield app
        finally:
            db.session.remove()
            db.drop_all()
            try:
                db.engine.dispose()
            except Exception:
                pass


@pytest.fixture
def test_user(app):
    """Create a test user"""
    with app.app_context():
        user = UserFactory()
        yield user


@pytest.fixture
def test_client(app):
    """Create a test client"""
    with app.app_context():
        client = ClientFactory()
        yield client


@pytest.fixture
def test_project(app, test_client, test_user):
    """Create a test project"""
    with app.app_context():
        project = ProjectFactory(client_id=test_client.id, billable=True, hourly_rate=Decimal("100.00"))
        yield project


@pytest.fixture
def test_invoice(app, test_project, test_user, test_client):
    """Create a test invoice"""
    with app.app_context():
        invoice = InvoiceFactory(
            project_id=test_project.id,
            client_id=test_client.id,
            created_by=test_user.id,
            client_name="Test Client",
            due_date=(date.today() + timedelta(days=30)),
        )
        # Ensure non-zero totals for payment-related assertions
        invoice.subtotal = Decimal("1000.00")
        invoice.tax_rate = Decimal("21.00")
        invoice.tax_amount = Decimal("210.00")
        invoice.total_amount = Decimal("1210.00")
        db.session.add(invoice)
        db.session.commit()
        yield invoice


class TestPaymentModel:
    """Test Payment model functionality"""

    def test_create_payment(self, app, test_invoice, test_user):
        """Test creating a payment"""
        with app.app_context():
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                method="bank_transfer",
                reference="REF-12345",
                notes="Test payment",
                status="completed",
                received_by=test_user.id,
            )

            db.session.add(payment)
            db.session.commit()

            # Verify payment was created
            assert payment.id is not None
            assert payment.amount == Decimal("500.00")
            assert payment.currency == "EUR"
            assert payment.method == "bank_transfer"
            assert payment.status == "completed"

            # Cleanup
            db.session.delete(payment)
            db.session.commit()

    def test_payment_calculate_net_amount_without_fee(self, app, test_invoice):
        """Test calculating net amount without gateway fee"""
        with app.app_context():
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                status="completed",
            )

            payment.calculate_net_amount()

            assert payment.net_amount == Decimal("500.00")

            # Cleanup (not in DB yet, so no cleanup needed)

    def test_payment_calculate_net_amount_with_fee(self, app, test_invoice):
        """Test calculating net amount with gateway fee"""
        with app.app_context():
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                gateway_fee=Decimal("15.00"),
                status="completed",
            )

            payment.calculate_net_amount()

            assert payment.net_amount == Decimal("485.00")

    def test_payment_to_dict(self, app, test_invoice, test_user):
        """Test converting payment to dictionary"""
        with app.app_context():
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                method="bank_transfer",
                reference="REF-12345",
                notes="Test payment",
                status="completed",
                received_by=test_user.id,
                gateway_fee=Decimal("15.00"),
                # created_at/updated_at set by defaults; no need to override
            )
            payment.calculate_net_amount()

            db.session.add(payment)
            db.session.commit()

            payment_dict = payment.to_dict()

            assert payment_dict["invoice_id"] == test_invoice.id
            assert payment_dict["amount"] == 500.0
            assert payment_dict["currency"] == "EUR"
            assert payment_dict["method"] == "bank_transfer"
            assert payment_dict["reference"] == "REF-12345"
            assert payment_dict["status"] == "completed"
            assert payment_dict["gateway_fee"] == 15.0
            assert payment_dict["net_amount"] == 485.0

            # Cleanup
            db.session.delete(payment)
            db.session.commit()

    def test_payment_relationship_with_invoice(self, app, test_invoice):
        """Test payment relationship with invoice"""
        with app.app_context():
            # Re-query invoice to attach to current session
            from app.models.invoice import Invoice

            invoice_in_session = Invoice.query.get(test_invoice.id)

            payment = PaymentFactory(
                invoice_id=invoice_in_session.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                status="completed",
            )

            db.session.add(payment)
            db.session.commit()

            # Refresh invoice to get updated relationships
            db.session.refresh(invoice_in_session)

            # Verify relationship
            assert payment.invoice == invoice_in_session
            assert payment in invoice_in_session.payments

            # Cleanup
            db.session.delete(payment)
            db.session.commit()

    def test_payment_relationship_with_user(self, app, test_invoice, test_user):
        """Test payment relationship with user (receiver)"""
        with app.app_context():
            # Re-query user to attach to current session
            from app.models.user import User

            user_in_session = User.query.get(test_user.id)

            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                status="completed",
                received_by=user_in_session.id,
            )

            db.session.add(payment)
            db.session.commit()

            # Refresh user to get updated relationships
            db.session.refresh(user_in_session)

            # Verify relationship
            assert payment.receiver == user_in_session
            assert payment in user_in_session.received_payments

            # Cleanup
            db.session.delete(payment)
            db.session.commit()

    def test_payment_repr(self, app, test_invoice):
        """Test payment string representation"""
        with app.app_context():
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("500.00"),
                currency="EUR",
                payment_date=date.today(),
                status="completed",
            )

            repr_str = repr(payment)
            assert "Payment" in repr_str
            assert "500.00" in repr_str
            assert "EUR" in repr_str

    def test_multiple_payments_for_invoice(self, app, test_invoice):
        """Test multiple payments for a single invoice"""
        with app.app_context():
            # Re-query invoice to attach to current session
            from app.models.invoice import Invoice

            invoice_in_session = Invoice.query.get(test_invoice.id)

            payment1 = PaymentFactory(
                invoice_id=invoice_in_session.id,
                amount=Decimal("300.00"),
                currency="EUR",
                payment_date=date.today(),
                status="completed",
            )

            payment2 = PaymentFactory(
                invoice_id=invoice_in_session.id,
                amount=Decimal("200.00"),
                currency="EUR",
                payment_date=date.today() + timedelta(days=1),
                status="completed",
            )

            db.session.add_all([payment1, payment2])
            db.session.commit()

            # Refresh invoice to get updated relationships
            db.session.refresh(invoice_in_session)

            # Verify both payments are associated with invoice
            assert invoice_in_session.payments.count() == 2

            # Cleanup
            db.session.delete(payment1)
            db.session.delete(payment2)
            db.session.commit()

    def test_payment_status_values(self, app, test_invoice):
        """Test different payment status values"""
        with app.app_context():
            statuses = ["completed", "pending", "failed", "refunded"]

            for status in statuses:
                payment = PaymentFactory(
                    invoice_id=test_invoice.id,
                    amount=Decimal("100.00"),
                    currency="EUR",
                    payment_date=date.today(),
                    status=status,
                )

                db.session.add(payment)
                db.session.commit()

                assert payment.status == status

                # Cleanup
                db.session.delete(payment)
                db.session.commit()


class TestPaymentIntegration:
    """Test Payment model integration with Invoice"""

    def test_invoice_updates_with_payment(self, app, test_invoice):
        """Test that invoice updates correctly when payment is added"""
        with app.app_context():
            # Initial state
            assert test_invoice.amount_paid == Decimal("0")
            assert test_invoice.payment_status == "unpaid"

            # Add payment
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=Decimal("605.00"),  # Half of total
                currency="EUR",
                payment_date=date.today(),
                status="completed",
            )

            db.session.add(payment)

            # Update invoice manually (this would be done by route logic)
            test_invoice.amount_paid = (test_invoice.amount_paid or Decimal("0")) + payment.amount
            test_invoice.update_payment_status()

            db.session.commit()

            # Verify invoice was updated
            assert test_invoice.amount_paid == Decimal("605.00")
            assert test_invoice.payment_status == "partially_paid"

            # Cleanup
            db.session.delete(payment)
            test_invoice.amount_paid = Decimal("0")
            test_invoice.update_payment_status()
            db.session.commit()

    def test_invoice_fully_paid_with_payments(self, app, test_invoice):
        """Test that invoice becomes fully paid when total payments equal total amount"""
        with app.app_context():
            # Add payments that equal total amount
            payment = PaymentFactory(
                invoice_id=test_invoice.id,
                amount=test_invoice.total_amount,
                currency="EUR",
                payment_date=date.today(),
                status="completed",
            )

            db.session.add(payment)

            # Update invoice manually (this would be done by route logic)
            test_invoice.amount_paid = payment.amount
            test_invoice.update_payment_status()

            db.session.commit()

            # Verify invoice is fully paid
            assert test_invoice.payment_status == "fully_paid"
            assert test_invoice.is_paid is True

            # Cleanup
            db.session.delete(payment)
            test_invoice.amount_paid = Decimal("0")
            test_invoice.update_payment_status()
            db.session.commit()
