"""Seed the demo wedding, three events, reminder schedules, email templates and the first
Super Admin.

Refuses to run when ENVIRONMENT=prod (task 8.8). Seeding fake guests over a real list days
before a wedding is unrecoverable, so the guard is a hard exit rather than a prompt.
"""

import asyncio
import os
import sys
from datetime import UTC, datetime, time, timedelta

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import get_settings
from app.db import get_session_factory
from app.models import (
    AdminUser,
    Event,
    Guest,
    Invitation,
    MessageTemplate,
    ReminderSchedule,
    Wedding,
)
from app.models.enums import (
    AdminRole,
    Channel,
    EventType,
    GuestSide,
    GuestSource,
    InvitationType,
    Locale,
    ReminderAudience,
    TemplatePurpose,
)
from app.services import slugs
from app.services.tokens import allocate_short_code, allocate_token

REMINDER_OFFSETS = (15, 7, 2)


def guard_environment() -> None:
    settings = get_settings()
    if settings.is_prod or os.getenv("ENVIRONMENT") == "prod":
        sys.exit("Refusing to seed: ENVIRONMENT is 'prod'. Seeding would overwrite real data.")


EMAIL_TEMPLATES: list[tuple[TemplatePurpose, Locale, str, str]] = [
    (
        TemplatePurpose.INVITE,
        Locale.EN,
        "You are invited to the {{event_title}} of {{couple_names}}",
        "Dear {{guest_name}},\n\nYou are warmly invited to the {{event_title}} of "
        "{{couple_names}} on {{event_date}} at {{venue}}.\n\n"
        "Please confirm your presence: {{invite_url}}\n\nDirections: {{map_url}}",
    ),
    (
        TemplatePurpose.INVITE,
        Locale.BN,
        "{{couple_names}}-এর {{event_title}} অনুষ্ঠানে আমন্ত্রণ",
        "প্রিয় {{guest_name}},\n\n{{couple_names}}-এর {{event_title}} অনুষ্ঠানে আপনাকে "
        "সাদর আমন্ত্রণ জানানো হচ্ছে। তারিখ {{event_date}}, স্থান {{venue}}।\n\n"
        "অনুগ্রহ করে উপস্থিতি নিশ্চিত করুন: {{invite_url}}",
    ),
    (
        TemplatePurpose.REMINDER_15,
        Locale.EN,
        "{{event_title}} in {{days_left}} days",
        "{{guest_name}}, {{event_title}} is {{days_left}} days away — {{event_date}} at "
        "{{venue}}.\n\nDirections: {{map_url}}\nCan't make it? {{cancel_url}}",
    ),
    (
        TemplatePurpose.REMINDER_7,
        Locale.EN,
        "{{event_title}} next week",
        "{{guest_name}}, just {{days_left}} days to go! {{event_title}} — {{event_date}}, "
        "{{event_time}} at {{venue}}.\n\nDirections: {{map_url}}\nCan't make it? {{cancel_url}}",
    ),
    (
        TemplatePurpose.REMINDER_2,
        Locale.EN,
        "{{event_title}} in 2 days",
        "{{guest_name}}, just 2 days to go! {{event_title}} — {{event_date}}, {{event_time}} "
        "at {{venue}}.\n\nDirections: {{map_url}}\nCan't make it? {{cancel_url}}",
    ),
    (
        TemplatePurpose.CANCEL_CONFIRM,
        Locale.EN,
        "Your RSVP for {{event_title}} is cancelled",
        "{{guest_name}}, we have cancelled your RSVP for {{event_title}}. We'll miss you.\n\n"
        "Changed your mind? {{invite_url}}",
    ),
    (
        TemplatePurpose.THANK_YOU,
        Locale.EN,
        "Thank you for celebrating with us",
        "{{guest_name}}, thank you for joining us at {{event_title}}. "
        "It meant a great deal to have you there.\n\n— {{couple_names}}",
    ),
]


async def ensure_super_admin(session: AsyncSession) -> str | None:
    """Create or promote the allowlisted Super Admin.

    Deliberately independent of the wedding check below: an operator re-running the seed
    to fix a missing admin should not be blocked just because the demo data already
    exists. Returns the email, or None if unset.
    """
    email = os.getenv("SEED_SUPER_ADMIN_EMAIL", "").strip().lower()
    if not email:
        print(
            "WARNING: SEED_SUPER_ADMIN_EMAIL is unset — no admin was created, "
            "so nobody will be able to sign in."
        )
        return None

    # Delegated so `make seed` and the boot-time provisioning cannot drift apart — one of
    # them silently promoting an account the other left alone is the kind of difference
    # nobody notices until a demotion comes back after a restart (design D8).
    from app.services.bootstrap import ensure_bootstrap_super_admin

    existing = await session.scalar(select(AdminUser).where(AdminUser.email == email))
    await ensure_bootstrap_super_admin(session)
    print(
        f"Super Admin already present: {email}"
        if existing is not None
        else f"Created Super Admin: {email}"
    )
    return email


#: Demo guests, seeded under every event (task 3.8).
#:
#: Rahim appears on all three lists on purpose — as three independent records sharing a
#: phone number. That is what event-scoped guests mean (design D11), and having it in the
#: seed means the first thing anyone sees in the admin reflects it rather than hiding it.
#: The phone digits are documentation-range and the addresses are `example.com`, so a stray
#: send in a dev stack cannot reach a real person.
DEMO_GUESTS: list[tuple[str, str, str, InvitationType]] = [
    ("Rahim Uddin", "+8801712345678", "rahim@example.com", InvitationType.FAMILY),
    ("Ayesha Siddika", "+8801812345679", "ayesha@example.com", InvitationType.SINGLE),
    ("Tanvir Hasan", "+8801912345680", "tanvir@example.com", InvitationType.SINGLE),
]


async def seed_guests(session: AsyncSession, event: Event) -> None:
    """Add the demo guests to one event, each with their own invitation and token.

    Written through the same shape the admin route uses: one guest row carrying `event_id`,
    one invitation pointing at that event. `invitations=[]` is set in the constructor rather
    than assigned afterwards — assigning to the relationship makes SQLAlchemy lazy-load the
    existing collection, which is illegal under asyncio.
    """
    for full_name, phone, email, invitation_type in DEMO_GUESTS:
        guest = Guest(
            wedding_id=event.wedding_id,
            event_id=event.id,
            full_name=full_name,
            email=email,
            phone_e164=phone,
            whatsapp_phone_e164=phone,
            side=GuestSide.COMMON,
            group_tag=["demo"],
            preferred_locale=Locale.EN,
            invitation_type=invitation_type,
            source=GuestSource.MANUAL,
            invitations=[],
        )
        session.add(guest)
        await session.flush()
        session.add(
            Invitation(
                event_id=event.id,
                guest_id=guest.id,
                token=await allocate_token(session),
                short_code=await allocate_short_code(session),
                max_guests=4 if invitation_type is InvitationType.FAMILY else 1,
            )
        )
    await session.flush()


async def seed() -> None:
    guard_environment()
    settings = get_settings()

    async with get_session_factory()() as session:
        await ensure_super_admin(session)

        # Every event needs an owner (design D1), and in a demo database that is the seeded
        # super admin. Fetched rather than assumed so the seed fails loudly here, with a
        # sentence, instead of at the first INSERT with a NOT NULL violation.
        owner = await session.scalar(
            select(AdminUser).where(AdminUser.role == AdminRole.SUPER_ADMIN).limit(1)
        )
        if owner is None:
            raise SystemExit(
                "No Super Admin exists, so seeded events would have no owner. "
                "Set SEED_SUPER_ADMIN_EMAIL and run the seed again."
            )

        existing = await session.scalar(select(Wedding).limit(1))
        if existing is not None:
            await session.commit()
            print(f"Wedding '{existing.slug}' already seeded — leaving it untouched.")
            return

        wedding = Wedding(
            bride_name="Nazifa",
            groom_name="Abdullah",
            slug="abdullah-weds-nazifa",
            default_locale=Locale.EN,
            timezone=settings.timezone,
            host_contact_phone="+8801700000000",
        )
        session.add(wedding)
        await session.flush()

        # Dates sit far enough out that all three reminder waves can still fire.
        base = datetime.now(UTC) + timedelta(days=45)
        # The bride's family hosts the Mehedi and the marriage, the groom's the Walima — two
        # different host blocks across one wedding, which is the whole reason the host lives
        # on the event rather than on the wedding. The hosts are parents, never the couple.
        bride_side = ("Kamrul Hasan", "Shirin Hasan", "+8801700000001")
        groom_side = ("Abdul Karim", None, "+8801700000002")

        event_specs = [
            (
                EventType.MEHEDI,
                "mehedi",
                "মেহেদী",
                "Mehedi",
                0,
                "Community Hall, Dhanmondi",
                bride_side,
            ),
            (
                EventType.MARRIAGE,
                "marriage",
                "বিবাহ",
                "Marriage Ceremony",
                2,
                "Grand Ballroom",
                bride_side,
            ),
            (
                # One host name and no second, so the seed exercises both renderings.
                EventType.WALIMA,
                "walima",
                "ওয়ালিমা",
                "Walima",
                3,
                "Rose Garden Convention",
                groom_side,
            ),
        ]

        for (
            event_type,
            theme,
            title_bn,
            title_en,
            day_offset,
            venue,
            (host_name_1, host_name_2, host_phone),
        ) in event_specs:
            event = Event(
                wedding_id=wedding.id,
                owner_admin_id=owner.id,
                type=event_type,
                # Allocated exactly as `POST /admin/events` does (task 3.8). The seed used to
                # hardcode `slug=` and so was the only place in the system that could produce
                # a slug the admin path would have rejected or suffixed.
                slug=await slugs.allocate(session, title_en, fallback=event_type.value),
                title_bn=title_bn,
                title_en=title_en,
                starts_at=base + timedelta(days=day_offset),
                venue_name=venue,
                venue_address=f"{venue}, Dhaka, Bangladesh",
                map_url="https://maps.google.com/?q=Dhaka",
                host_name_1=host_name_1,
                host_name_2=host_name_2,
                host_phone=host_phone,
                theme_key=theme,
                rsvp_deadline=base + timedelta(days=day_offset - 1),
                is_published=True,
            )
            session.add(event)
            await session.flush()

            for offset in REMINDER_OFFSETS:
                session.add(
                    ReminderSchedule(
                        event_id=event.id,
                        offset_days=offset,
                        send_at_local_time=time(10, 0),
                        channels=["email"],
                        audience=ReminderAudience.ACCEPTED,
                        is_enabled=True,
                    )
                )

            await seed_guests(session, event)

        for purpose, locale, subject, body in EMAIL_TEMPLATES:
            session.add(
                MessageTemplate(
                    channel=Channel.EMAIL,
                    purpose=purpose,
                    locale=locale,
                    subject=subject,
                    body=body,
                    body_text=body,
                    is_active=True,
                )
            )

        await session.commit()
        slug = wedding.slug

    print(
        f"Seeded wedding '{slug}' with 3 events, "
        f"{len(DEMO_GUESTS)} guests per event, reminder schedules and templates."
    )


if __name__ == "__main__":
    asyncio.run(seed())
