"""
Tests for invoice email sending functionality
"""

import pytest
from unittest.mock import patch, MagicMock
from datetime import datetime, date, timedelta
from decimal import Decimal
from flask import current_app
from app import db
from app.models import Invoice, InvoiceEmail, User, Settings, Client, Project
from app.utils.email import send_invoice_email, send_invoice_template_test_email
from factories import UserFactory, ClientFactory, ProjectFactory, InvoiceFactory, InvoiceItemFactory


@pytest.fixture
def test_user(app):
    """Create a test user"""
    user = UserFactory(username="testuser", role="user")
    db.session.add(user)
    db.session.commit()
    return user


@pytest.fixture
def test_client(app):
    """Create a test client"""
    client = ClientFactory(name="Test Client", email="client@test.com")
    db.session.commit()
    return client


@pytest.fixture
def test_project(app, test_client):
    """Create a test project"""
    project = ProjectFactory(
        name="Test Project", client_id=test_client.id, billable=True, hourly_rate=Decimal("100.00")
    )
    db.session.commit()
    return project


@pytest.fixture
def test_invoice(app, test_user, test_project, test_client):
    """Create a test invoice with items"""
    invoice = InvoiceFactory(
        invoice_number="INV-2024-001",
        project_id=test_project.id,
        client_id=test_client.id,
        client_name=test_client.name,
        client_email=test_client.email,
        due_date=date.today() + timedelta(days=30),
        created_by=test_user.id,
        status="draft",
        subtotal=Decimal("1000.00"),
        tax_rate=Decimal("20.00"),
        tax_amount=Decimal("200.00"),
        total_amount=Decimal("1200.00"),
        currency_code="EUR",
    )
    db.session.commit()

    # Add invoice item
    item = InvoiceItemFactory(
        invoice_id=invoice.id,
        description="Test Service",
        quantity=Decimal("10.00"),
        unit_price=Decimal("100.00"),
        total_amount=Decimal("1000.00"),
    )
    db.session.commit()

    return invoice


@pytest.fixture
def mock_pdf_generator():
    """Mock PDF generator"""
    with patch("app.utils.email.InvoicePDFGenerator") as mock_gen:
        mock_instance = MagicMock()
        mock_instance.generate_pdf.return_value = b"fake_pdf_bytes"
        mock_gen.return_value = mock_instance
        yield mock_instance


@pytest.fixture
def mock_mail_send():
    """Mock mail.send"""
    with patch("app.utils.email.mail.send") as mock_send:
        yield mock_send


class TestSendInvoiceEmail:
    """Tests for send_invoice_email function"""

    def test_send_invoice_email_success(self, app, test_invoice, test_user, mock_pdf_generator, mock_mail_send):
        """Test successfully sending an invoice email"""
        with app.app_context():
            # Configure mail server
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
            )

            assert success is True
            assert invoice_email is not None
            assert invoice_email.recipient_email == "client@test.com"
            assert invoice_email.invoice_id == test_invoice.id
            assert invoice_email.sent_by == test_user.id
            assert invoice_email.status == "sent"
            assert "successfully" in message.lower()
            assert mock_mail_send.called

            # Verify invoice status was updated
            db.session.refresh(test_invoice)
            assert test_invoice.status == "sent"

    def test_send_invoice_email_with_custom_message(
        self, app, test_invoice, test_user, mock_pdf_generator, mock_mail_send
    ):
        """Test sending invoice email with custom message"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            custom_message = "Thank you for your business!"
            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice,
                recipient_email="client@test.com",
                sender_user=test_user,
                custom_message=custom_message,
            )

            assert success is True
            assert invoice_email is not None
            # Verify the message was sent (check mail.send was called with message containing custom text)
            assert mock_mail_send.called

    def test_send_invoice_email_pdf_generation_failure(self, app, test_invoice, test_user, mock_mail_send):
        """Test handling PDF generation failure"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            # Mock PDF generator to fail
            with patch("app.utils.email.InvoicePDFGenerator") as mock_gen:
                mock_instance = MagicMock()
                mock_instance.generate_pdf.side_effect = Exception("PDF generation failed")
                mock_gen.return_value = mock_instance

                # Mock fallback generator to also fail
                with patch("app.utils.email.InvoicePDFGeneratorFallback") as mock_fallback:
                    mock_fallback_instance = MagicMock()
                    mock_fallback_instance.generate_pdf.side_effect = Exception("Fallback failed")
                    mock_fallback.return_value = mock_fallback_instance

                    success, invoice_email, message = send_invoice_email(
                        invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
                    )

                    assert success is False
                    assert invoice_email is None
                    assert "pdf generation failed" in message.lower() or "failed" in message.lower()

    def test_send_invoice_email_no_mail_server(self, app, test_invoice, test_user, mock_pdf_generator):
        """Test sending email when mail server is not configured"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = None

            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
            )

            # Should still attempt to send but may fail gracefully
            # The function should handle this case
            assert invoice_email is not None or success is False

    def test_send_invoice_email_creates_tracking_record(
        self, app, test_invoice, test_user, mock_pdf_generator, mock_mail_send
    ):
        """Test that email tracking record is created"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            # Count existing records
            initial_count = InvoiceEmail.query.filter_by(invoice_id=test_invoice.id).count()

            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
            )

            assert success is True

            # Verify record was created
            final_count = InvoiceEmail.query.filter_by(invoice_id=test_invoice.id).count()
            assert final_count == initial_count + 1

            # Verify record details
            assert invoice_email.recipient_email == "client@test.com"
            assert invoice_email.invoice_id == test_invoice.id
            assert invoice_email.sent_by == test_user.id

    def test_send_invoice_email_updates_draft_status(
        self, app, test_invoice, test_user, mock_pdf_generator, mock_mail_send
    ):
        """Test that draft invoice status is updated to 'sent'"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            # Ensure invoice is in draft status
            test_invoice.status = "draft"
            db.session.commit()

            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
            )

            assert success is True

            # Verify status was updated
            db.session.refresh(test_invoice)
            assert test_invoice.status == "sent"

    def test_send_invoice_email_does_not_update_non_draft_status(
        self, app, test_invoice, test_user, mock_pdf_generator, mock_mail_send
    ):
        """Test that non-draft invoice status is not changed"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            # Set invoice to 'sent' status
            test_invoice.status = "sent"
            db.session.commit()

            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
            )

            assert success is True

            # Verify status remained 'sent'
            db.session.refresh(test_invoice)
            assert test_invoice.status == "sent"

    def test_send_invoice_email_with_email_template(
        self, app, test_invoice, test_user, mock_pdf_generator, mock_mail_send
    ):
        """Test sending invoice email with custom email template"""
        with app.app_context():
            from app.models import InvoiceTemplate

            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            # Create an email template
            template = InvoiceTemplate(
                name="Test Template",
                html="<html><body><h1>Invoice {{ invoice.invoice_number }}</h1></body></html>",
                css="body { color: black; }",
            )
            db.session.add(template)
            db.session.commit()

            success, invoice_email, message = send_invoice_email(
                invoice=test_invoice,
                recipient_email="client@test.com",
                sender_user=test_user,
                email_template_id=template.id,
            )

            assert success is True
            assert invoice_email is not None
            assert mock_mail_send.called

    def test_send_invoice_email_failure_creates_failed_record(self, app, test_invoice, test_user, mock_pdf_generator):
        """Test that failed email sends create a failed tracking record"""
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            # Mock mail.send to raise an exception
            with patch("app.utils.email.mail.send") as mock_send:
                mock_send.side_effect = Exception("SMTP connection failed")

                success, invoice_email, message = send_invoice_email(
                    invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
                )

                assert success is False
                # Should create a failed record
                failed_record = InvoiceEmail.query.filter_by(invoice_id=test_invoice.id, status="failed").first()
                assert failed_record is not None
                assert failed_record.error_message is not None


class TestInvoiceEmailRoutes:
    """Tests for invoice email routes"""

    def test_send_invoice_email_route_success(
        self, client, test_user, test_invoice, mock_pdf_generator, mock_mail_send
    ):
        """Test the send invoice email route"""
        with client.session_transaction() as sess:
            sess["_user_id"] = str(test_user.id)
            sess["_fresh"] = True

        response = client.post(
            f"/invoices/{test_invoice.id}/send-email",
            data={"recipient_email": "client@test.com", "csrf_token": "test_token"},
        )

        # Should return success (may need to handle CSRF token properly in test)
        assert response.status_code in [200, 400, 403]  # 400/403 if CSRF fails

    def test_get_invoice_email_history(self, client, test_user, test_invoice, mock_pdf_generator, mock_mail_send):
        """Test getting invoice email history"""
        with client.session_transaction() as sess:
            sess["_user_id"] = str(test_user.id)
            sess["_fresh"] = True

        # First send an email
        with client.application.app_context():
            from app.utils.email import send_invoice_email

            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            send_invoice_email(invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user)

        # Then get history
        response = client.get(f"/invoices/{test_invoice.id}/email-history")

        # Should return success (may need to handle authentication properly)
        assert response.status_code in [200, 401, 403]

    def test_resend_invoice_email_route(self, client, test_user, test_invoice, mock_pdf_generator, mock_mail_send):
        """Test the resend invoice email route"""
        with client.session_transaction() as sess:
            sess["_user_id"] = str(test_user.id)
            sess["_fresh"] = True

        # First send an email to create a record
        with client.application.app_context():
            from app.utils.email import send_invoice_email

            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            success, invoice_email, _ = send_invoice_email(
                invoice=test_invoice, recipient_email="client@test.com", sender_user=test_user
            )

            if success and invoice_email:
                # Then resend it
                response = client.post(
                    f"/invoices/{test_invoice.id}/resend-email/{invoice_email.id}",
                    data={"recipient_email": "client@test.com", "csrf_token": "test_token"},
                )

                # Should return success (may need to handle CSRF token properly)
                assert response.status_code in [200, 400, 403]


class TestInvoiceEmailModel:
    """Tests for InvoiceEmail model"""

    def test_invoice_email_creation(self, app, test_invoice, test_user):
        """Test creating an InvoiceEmail record"""
        with app.app_context():
            invoice_email = InvoiceEmail(
                invoice_id=test_invoice.id,
                recipient_email="client@test.com",
                subject="Test Invoice",
                sent_by=test_user.id,
            )
            db.session.add(invoice_email)
            db.session.commit()

            assert invoice_email.id is not None
            assert invoice_email.invoice_id == test_invoice.id
            assert invoice_email.recipient_email == "client@test.com"
            assert invoice_email.status == "sent"
            assert invoice_email.sent_at is not None

    def test_invoice_email_mark_opened(self, app, test_invoice, test_user):
        """Test marking email as opened"""
        with app.app_context():
            invoice_email = InvoiceEmail(
                invoice_id=test_invoice.id,
                recipient_email="client@test.com",
                subject="Test Invoice",
                sent_by=test_user.id,
            )
            db.session.add(invoice_email)
            db.session.commit()

            invoice_email.mark_opened()
            db.session.commit()

            assert invoice_email.status == "opened"
            assert invoice_email.opened_at is not None
            assert invoice_email.opened_count == 1

    def test_invoice_email_mark_failed(self, app, test_invoice, test_user):
        """Test marking email as failed"""
        with app.app_context():
            invoice_email = InvoiceEmail(
                invoice_id=test_invoice.id,
                recipient_email="client@test.com",
                subject="Test Invoice",
                sent_by=test_user.id,
            )
            db.session.add(invoice_email)
            db.session.commit()

            error_message = "SMTP connection failed"
            invoice_email.mark_failed(error_message)
            db.session.commit()

            assert invoice_email.status == "failed"
            assert invoice_email.error_message == error_message

    def test_invoice_email_to_dict(self, app, test_invoice, test_user):
        """Test converting InvoiceEmail to dictionary"""
        with app.app_context():
            invoice_email = InvoiceEmail(
                invoice_id=test_invoice.id,
                recipient_email="client@test.com",
                subject="Test Invoice",
                sent_by=test_user.id,
            )
            db.session.add(invoice_email)
            db.session.commit()

            email_dict = invoice_email.to_dict()

            assert isinstance(email_dict, dict)
            assert email_dict["invoice_id"] == test_invoice.id
            assert email_dict["recipient_email"] == "client@test.com"
            assert email_dict["subject"] == "Test Invoice"
            assert email_dict["status"] == "sent"
            assert "sent_at" in email_dict
            assert "created_at" in email_dict


class TestSendInvoiceTemplateTestEmail:
    """Tests for send_invoice_template_test_email"""

    @patch("app.utils.pdf_generator.InvoicePDFGenerator")
    def test_send_invoice_template_test_email_success(
        self, mock_pdf_class, app, test_invoice, mock_mail_send
    ):
        mock_instance = MagicMock()
        mock_instance.generate_pdf.return_value = b"fake_pdf_bytes"
        mock_pdf_class.return_value = mock_instance

        with app.app_context():
            from app.models import InvoiceTemplate

            current_app.config["MAIL_SERVER"] = "smtp.test.com"
            current_app.config["MAIL_DEFAULT_SENDER"] = "noreply@test.com"

            tmpl = InvoiceTemplate(name="Tpl", html="<p>{{ invoice.invoice_number }}</p>", css="")
            db.session.add(tmpl)
            db.session.commit()

            success, message = send_invoice_template_test_email(
                tmpl.id, "preview@example.com", invoice_id=test_invoice.id
            )
            assert success is True
            assert "successfully" in message.lower()
            mock_mail_send.assert_called_once()

    def test_send_invoice_template_test_email_no_mail_server(self, app, test_invoice):
        with app.app_context():
            from app.models import InvoiceTemplate

            current_app.config["MAIL_SERVER"] = "localhost"
            tmpl = InvoiceTemplate(name="Tpl2", html="<p>x</p>")
            db.session.add(tmpl)
            db.session.commit()

            success, message = send_invoice_template_test_email(
                tmpl.id, "preview@example.com", invoice_id=test_invoice.id
            )
            assert success is False
            assert "not configured" in message.lower()

    def test_send_invoice_template_test_email_template_missing(self, app, test_invoice):
        with app.app_context():
            current_app.config["MAIL_SERVER"] = "smtp.test.com"

            success, message = send_invoice_template_test_email(
                999999, "preview@example.com", invoice_id=test_invoice.id
            )
            assert success is False
            assert "not found" in message.lower()
