import unittest
from datetime import date, timedelta
from decimal import Decimal

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from app.core.database import Base
from app.core.models import (
    Customer,
    EmailLog,
    EmailTemplate,
    Invoice,
    InvoiceStatus,
    Product,
    Reminder,
    ReminderType,
    Subscription,
    SubscriptionStatus,
    TelegramLog,
)
from app.services import reminder_service


class ReminderServiceTests(unittest.IsolatedAsyncioTestCase):
    def setUp(self):
        engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False})
        Base.metadata.create_all(engine)
        self.Session = sessionmaker(bind=engine)
        self.db = self.Session()
        self.sent_emails = []
        self.sent_telegrams = []
        self.original_send_email = reminder_service.send_email
        self.original_send_telegram = reminder_service.send_telegram_message

        async def fake_send_email(recipient, subject, body):
            self.sent_emails.append((recipient, subject, body))
            return {"success": True}

        async def fake_send_telegram(chat_id, message):
            self.sent_telegrams.append((chat_id, message))
            return {"success": True}

        reminder_service.send_email = fake_send_email
        reminder_service.send_telegram_message = fake_send_telegram

    def tearDown(self):
        reminder_service.send_email = self.original_send_email
        reminder_service.send_telegram_message = self.original_send_telegram
        self.db.close()

    def _customer(self):
        customer = Customer(name="Acme Ltd", email="billing@acme.test")
        self.db.add(customer)
        self.db.commit()
        return customer

    def _product(self):
        product = Product(name="Managed Hosting", unit_price=Decimal("100.00"))
        self.db.add(product)
        self.db.commit()
        return product

    async def test_invoice_overdue_respects_trigger_days_threshold(self):
        customer = self._customer()
        invoice = Invoice(
            invoice_number="INV-7",
            customer_id=customer.id,
            invoice_date=date.today() - timedelta(days=30),
            due_date=date.today() - timedelta(days=3),
            total_amount=Decimal("250.00"),
            currency="NZD",
            status=InvoiceStatus.sent,
        )
        reminder = Reminder(
            reminder_type=ReminderType.invoice_overdue,
            trigger_days=7,
            send_email=True,
            send_telegram=True,
            status="active",
        )
        self.db.add_all([invoice, reminder])
        self.db.commit()

        results = reminder_service._empty_results()
        await reminder_service._check_invoice_overdue(self.db, reminder, date.today(), "chat-1", results)

        self.assertEqual(0, len(self.sent_emails))
        self.assertEqual(0, len(self.sent_telegrams))
        self.assertEqual(0, self.db.query(EmailLog).count())
        self.assertEqual(0, results["invoice_overdue"]["sent"])

    async def test_same_day_rerun_does_not_send_duplicate_invoice_reminder(self):
        customer = self._customer()
        invoice = Invoice(
            invoice_number="INV-1",
            customer_id=customer.id,
            invoice_date=date.today() - timedelta(days=30),
            due_date=date.today() - timedelta(days=8),
            total_amount=Decimal("125.00"),
            currency="NZD",
            status=InvoiceStatus.sent,
        )
        reminder = Reminder(
            reminder_type=ReminderType.invoice_overdue,
            trigger_days=7,
            send_email=True,
            send_telegram=True,
            status="active",
        )
        self.db.add_all([invoice, reminder])
        self.db.commit()

        for _ in range(2):
            results = reminder_service._empty_results()
            await reminder_service._check_invoice_overdue(self.db, reminder, date.today(), "chat-1", results)

        self.assertEqual(1, len(self.sent_emails))
        self.assertEqual(1, len(self.sent_telegrams))
        self.assertEqual(1, self.db.query(EmailLog).count())
        self.assertEqual(1, self.db.query(TelegramLog).count())

    async def test_email_and_telegram_templates_render_context_variables(self):
        customer = self._customer()
        product = self._product()
        template = EmailTemplate(
            name="Expiry",
            subject="{customer_name} renewal for {product_name}",
            body="Hi {customer_name}, {product_name} ends in {days_left} days on {end_date}.",
        )
        subscription = Subscription(
            customer_id=customer.id,
            product_id=product.id,
            start_date=date.today() - timedelta(days=20),
            end_date=date.today() + timedelta(days=2),
            status=SubscriptionStatus.active,
            auto_renew=True,
        )
        reminder = Reminder(
            reminder_type=ReminderType.subscription_expiry,
            trigger_days=7,
            send_email=True,
            send_telegram=True,
            email_template=template,
            telegram_template="Internal: {customer_name}/{product_name}/{days_left}/{auto_renew}",
            status="active",
        )
        self.db.add_all([template, subscription, reminder])
        self.db.commit()

        results = reminder_service._empty_results()
        await reminder_service._check_subscription_expiry(self.db, reminder, date.today(), "chat-1", results)

        self.assertEqual(
            ("billing@acme.test", "Acme Ltd renewal for Managed Hosting", f"Hi Acme Ltd, Managed Hosting ends in 2 days on {subscription.end_date}."),
            self.sent_emails[0],
        )
        self.assertEqual(("chat-1", "Internal: Acme Ltd/Managed Hosting/2/Yes"), self.sent_telegrams[0])

    async def test_custom_reminders_are_reported_as_skipped(self):
        reminder = Reminder(reminder_type=ReminderType.custom, status="active")
        self.db.add(reminder)
        self.db.commit()

        results = reminder_service._empty_results()
        await reminder_service._process_reminder(self.db, reminder, date.today(), "chat-1", results)

        self.assertEqual(1, results["custom"]["skipped"])
        self.assertEqual(0, len(self.sent_emails))
        self.assertEqual(0, len(self.sent_telegrams))


if __name__ == "__main__":
    unittest.main()
