"""Single demo seed: HR, Phonebook (contacts/orgs/donors), Projects, Events.

Replaces the old per-app seed zoo. Dependency-safe order:

  1. Companies (organizations + donors) and org departments.
  2. Users — creating a User auto-creates an Employee stub (hr.signals) which
     in turn auto-creates a Contact. We UPDATE those stubs, never re-create.
  3. Directors (link to employees).
  4. Standalone phonebook contacts (linked to organizations).
  5. Finance budget catalog (categories + items) — needed for fund allocations.
  6. Projects + funds + allocations + finance ledger + team + schedule.
  7. Events + invitations + speakers + schedule + registrations.

RBAC roles/permissions are NOT touched here — run `migrate` (data migrations)
and `seed_rbac` for those.

Usage:
    python manage.py seed_demo
    python manage.py seed_demo --reset    # wipe demo domain data first, then reseed
"""
from datetime import timedelta
from decimal import Decimal

from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone

User = get_user_model()

ADMIN_EMAIL = "admin@gmail.com"
DEMO_PASSWORD = "demo12345"

DEPARTMENTS = [
    ("Politics & Security", "POL"),
    ("Economics", "ECO"),
    ("International Relations", "IR"),
    ("Operations", "OPS"),
    ("Finance & Admin", "FIN"),
]

# (name, position, department, employment_type)
PEOPLE = [
    ("Andi", "Wijaya", "Executive Director", "Operations", "staff"),
    ("Siti", "Nurhaliza", "Deputy Director", "Operations", "staff"),
    ("Budi", "Santoso", "Senior Researcher", "Politics & Security", "staff"),
    ("Dewi", "Lestari", "Researcher", "Politics & Security", "staff"),
    ("Eko", "Prasetyo", "Senior Economist", "Economics", "staff"),
    ("Fitri", "Handayani", "Economist", "Economics", "staff"),
    ("Gunawan", "Halim", "IR Analyst", "International Relations", "staff"),
    ("Hana", "Permata", "IR Analyst", "International Relations", "staff"),
    ("Indra", "Kusuma", "Finance Manager", "Finance & Admin", "staff"),
    ("Joko", "Susilo", "Accountant", "Finance & Admin", "staff"),
    ("Kartika", "Sari", "HR Officer", "Finance & Admin", "staff"),
    ("Lukman", "Hakim", "Operations Officer", "Operations", "staff"),
    ("Maya", "Anggraini", "Communications Officer", "Operations", "staff"),
    ("Nanda", "Pratama", "Research Fellow", "Economics", "fellow"),
    ("Oki", "Setiawan", "Research Fellow", "Politics & Security", "fellow"),
    ("Putri", "Maharani", "Research Fellow", "International Relations", "fellow"),
    ("Qori", "Ananda", "Program Intern", "Operations", "intern"),
    ("Rani", "Oktaviani", "Research Intern", "Economics", "intern"),
    ("Sandi", "Firmansyah", "Research Intern", "Politics & Security", "intern"),
    ("Tari", "Wulandari", "Comms Intern", "Operations", "intern"),
    ("Umar", "Salim", "Senior Researcher", "International Relations", "staff"),
    ("Vina", "Marlina", "Researcher", "Economics", "staff"),
    ("Wawan", "Hidayat", "IT Officer", "Finance & Admin", "staff"),
    ("Yuni", "Astuti", "Admin Officer", "Finance & Admin", "staff"),
    ("Zaki", "Ramadhan", "Researcher", "Politics & Security", "staff"),
]

# (name, abbreviation, type, donor_category, industry, country)
ORGANIZATIONS = [
    ("Ford Foundation", "FORD", "donor", "regular", "Philanthropy", "United States"),
    ("Open Society Foundations", "OSF", "donor", "regular", "Philanthropy", "United States"),
    ("Konrad Adenauer Stiftung", "KAS", "donor", "regular", "Political Foundation", "Germany"),
    ("Friedrich Naumann Foundation", "FNF", "donor", "regular", "Political Foundation", "Germany"),
    ("The Asia Foundation", "TAF", "donor", "regular", "Development", "United States"),
    ("Australian DFAT", "DFAT", "donor", "project", "Government", "Australia"),
    ("USAID", "USAID", "donor", "project", "Government", "United States"),
    ("European Union Delegation", "EU", "donor", "project", "Government", "Belgium"),
    ("Hanns Seidel Foundation", "HSF", "donor", "regular", "Political Foundation", "Germany"),
    ("Japan Foundation", "JF", "donor", "regular", "Cultural", "Japan"),
    ("Bappenas", "BAPPENAS", "organization", "", "Government", "Indonesia"),
    ("Kementerian Luar Negeri", "KEMLU", "organization", "", "Government", "Indonesia"),
    ("Universitas Indonesia", "UI", "organization", "", "Academia", "Indonesia"),
    ("Gadjah Mada University", "UGM", "organization", "", "Academia", "Indonesia"),
    ("LIPI / BRIN", "BRIN", "organization", "", "Research", "Indonesia"),
    ("ASEAN Secretariat", "ASEAN", "organization", "", "Multilateral", "Indonesia"),
    ("World Bank Indonesia", "WB", "organization", "", "Multilateral", "Indonesia"),
    ("Tempo Media Group", "TEMPO", "organization", "", "Media", "Indonesia"),
    ("Kompas Gramedia", "KOMPAS", "organization", "", "Media", "Indonesia"),
    ("Bank Indonesia", "BI", "organization", "", "Government", "Indonesia"),
    ("Kadin Indonesia", "KADIN", "organization", "", "Business", "Indonesia"),
    ("CSIS Jakarta Partner", "CSISP", "organization", "", "Think Tank", "Indonesia"),
    ("Lowy Institute", "LOWY", "organization", "", "Think Tank", "Australia"),
    ("ISEAS Singapore", "ISEAS", "organization", "", "Think Tank", "Singapore"),
    ("Brookings Institution", "BROOK", "organization", "", "Think Tank", "United States"),
]

# Standalone phonebook contacts (external people), assigned to org by abbreviation.
EXTERNAL_CONTACTS = [
    ("Rachel Tan", "rachel.tan@asiafoundation.org", "Program Director", "TAF"),
    ("Michael Chen", "m.chen@fordfoundation.org", "Senior Program Officer", "FORD"),
    ("Anke Müller", "anke.mueller@kas.de", "Country Representative", "KAS"),
    ("Dr. Sarah Wong", "s.wong@iseas.edu.sg", "Senior Fellow", "ISEAS"),
    ("James O'Brien", "j.obrien@lowyinstitute.org", "Research Director", "LOWY"),
    ("Prof. Bambang Sutrisno", "bambang@ui.ac.id", "Dean", "UI"),
    ("Ibu Retno Lestari", "retno@kemlu.go.id", "Director of Policy", "KEMLU"),
    ("Pak Hendra Wijaya", "hendra@bappenas.go.id", "Senior Planner", "BAPPENAS"),
    ("Lisa Anderson", "l.anderson@usaid.gov", "Mission Director", "USAID"),
    ("Tomoko Yamada", "t.yamada@jpf.go.jp", "Program Manager", "JF"),
    ("David Smith", "d.smith@worldbank.org", "Country Economist", "WB"),
    ("Maria Santos", "m.santos@eeas.europa.eu", "Political Counsellor", "EU"),
    ("Peter Hartono", "peter@tempo.co.id", "Senior Editor", "TEMPO"),
    ("Nadia Rahman", "nadia@kompas.com", "Bureau Chief", "KOMPAS"),
    ("Dr. Agus Salim", "agus@brin.go.id", "Research Professor", "BRIN"),
    ("Grace Liu", "grace.liu@brookings.edu", "Fellow", "BROOK"),
    ("Hiro Tanaka", "hiro@asean.org", "Programme Officer", "ASEAN"),
    ("Sinta Dewi", "sinta@kadin.id", "Policy Advisor", "KADIN"),
    ("Robert Kim", "r.kim@dfat.gov.au", "Counsellor", "DFAT"),
    ("Farah Diba", "farah@bi.go.id", "Senior Analyst", "BI"),
]

# (name, status, lead_index_into_staff, donor_abbrev, fund_amount, currency)
PROJECTS = [
    ("Indo-Pacific Maritime Security Study", "active", 2, "DFAT", 850000, "AUD"),
    ("ASEAN Economic Integration Outlook", "active", 4, "USAID", 1200000, "USD"),
    ("Democratic Resilience in Southeast Asia", "active", 3, "FORD", 600000, "USD"),
    ("Digital Economy Policy Lab", "planning", 5, "EU", 950000, "EUR"),
    ("Climate Diplomacy Initiative", "active", 6, "KAS", 400000, "EUR"),
    ("Regional Trade Architecture Review", "completed", 4, "TAF", 350000, "USD"),
    ("Election Integrity Monitoring", "on_hold", 14, "OSF", 500000, "USD"),
    ("Youth Leadership Forum", "active", 12, "FNF", 200000, "EUR"),
    ("Defense Modernization Watch", "active", 20, "DFAT", 700000, "AUD"),
    ("Indonesia 2045 Foresight", "planning", 0, "FORD", 1500000, "USD"),
    ("Maritime Boundary Dialogue", "active", 7, "JF", 300000, "JPY"),
    ("Green Investment Tracker", "active", 5, "EU", 800000, "EUR"),
]

EVENT_TYPES = ["conference", "seminar", "workshop", "webinar", "dissemination", "meeting"]

EVENTS = [
    ("CSIS Annual Conference 2026", "conference", "Hotel Mulia Jakarta", "published", 300),
    ("Indo-Pacific Security Forum", "conference", "Shangri-La Jakarta", "published", 200),
    ("ASEAN Economic Outlook Launch", "dissemination", "CSIS Auditorium", "published", 120),
    ("Digital Economy Policy Workshop", "workshop", "CSIS Meeting Room A", "published", 40),
    ("Climate Diplomacy Roundtable", "seminar", "CSIS Meeting Room B", "published", 30),
    ("Trade Architecture Webinar", "webinar", "Online (Zoom)", "published", 500),
    ("Democratic Resilience Seminar", "seminar", "CSIS Auditorium", "completed", 80),
    ("Youth Leadership Forum 2026", "conference", "Bali Convention Center", "draft", 150),
    ("Maritime Security Expert Meeting", "meeting", "CSIS Board Room", "published", 20),
    ("Green Investment Dissemination", "dissemination", "CSIS Auditorium", "draft", 100),
    ("Defense Modernization Briefing", "seminar", "CSIS Meeting Room A", "published", 50),
    ("Indonesia 2045 Foresight Workshop", "workshop", "CSIS Meeting Room B", "draft", 35),
    ("Regional Trade Webinar Series", "webinar", "Online (Teams)", "published", 400),
    ("ASEAN Connectivity Conference", "conference", "Hotel Indonesia Kempinski", "published", 250),
    ("Policy Communications Training", "workshop", "CSIS Training Room", "completed", 25),
]

BUDGET_CATALOG = [
    ("A", "Personnel", ["Salaries", "Honoraria", "Benefits"]),
    ("B", "Activities", ["Workshops", "Field Research", "Travel", "Publications"]),
    ("C", "Operations", ["Office Rent", "Utilities", "Equipment"]),
    ("D", "Overhead", ["Administrative", "Audit & Compliance"]),
]


class Command(BaseCommand):
    help = "Seed demo data for HR, Phonebook, Projects, and Events (one command)."

    def add_arguments(self, parser):
        parser.add_argument(
            "--reset", action="store_true",
            help="Wipe demo domain data (keeps superusers + RBAC) before reseeding.",
        )

    @transaction.atomic
    def handle(self, *args, **opts):
        if opts["reset"]:
            self._reset()

        self.today = timezone.now().date()
        admin = self._ensure_admin()
        self.org = admin.organizations.first()

        companies = self._seed_companies()
        self._seed_departments()
        employees = self._seed_people()
        self._seed_directors(employees)
        self._seed_contacts(companies)
        budget_items = self._seed_budget_catalog()
        self._seed_projects(employees, companies, budget_items)
        self._seed_events(admin, employees)

        self._summary()

    # ------------------------------------------------------------------ reset
    def _reset(self):
        from apps.companies.models import Company, Director, Department
        from apps.contacts.models import Contact
        from apps.projects.models import Project, ProjectFund
        from apps.events.models import Event
        from apps.finance.models import BudgetCategory
        from apps.hr.models import Employee

        self.stdout.write(self.style.WARNING("Resetting demo domain data…"))
        Event.objects.all().delete()
        Project.objects.all().delete()
        ProjectFund.objects.all().delete()
        BudgetCategory.objects.all().delete()
        Director.objects.all().delete()
        Department.objects.all().delete()
        Contact.objects.all().delete()
        Company.objects.all().delete()
        # Drop non-superuser users → cascades Employees + their Contacts.
        User.objects.filter(is_superuser=False).delete()
        Employee.objects.exclude(user__is_superuser=True).delete()

    # ------------------------------------------------------------------ admin
    def _ensure_admin(self):
        admin = User.objects.filter(email=ADMIN_EMAIL).first()
        if not admin:
            admin = User.objects.create_superuser(
                email=ADMIN_EMAIL, password="687654",
                first_name="Admin", last_name="KnowledgeHub",
            )
            self.stdout.write(self.style.SUCCESS(f"Created superuser {ADMIN_EMAIL}"))
        return admin

    # -------------------------------------------------------------- companies
    def _seed_companies(self):
        from apps.companies.models import Company

        companies = {}
        for name, abbr, ctype, dcat, industry, country in ORGANIZATIONS:
            company, _ = Company.objects.update_or_create(
                organization=self.org,
                name=name,
                defaults=dict(
                    abbreviation=abbr,
                    company_type=ctype,
                    donor_category=dcat,
                    industry=industry,
                    country=country,
                    email=f"info@{abbr.lower()}.org",
                    website=f"https://www.{abbr.lower()}.org",
                ),
            )
            companies[abbr] = company
        self.stdout.write(f"  companies: {len(companies)}")
        return companies

    def _seed_departments(self):
        from apps.companies.models import Department

        for name, code in DEPARTMENTS:
            Department.objects.update_or_create(organization=self.org, name=name, defaults={"code": code})
        self.stdout.write(f"  departments: {len(DEPARTMENTS)}")

    # ------------------------------------------------------------------- people
    def _seed_people(self):
        """Create users; the post_save signal auto-creates Employee stubs which
        we then update with real position/department. Returns staff Employees."""
        from apps.hr.models import Employee

        employees = []
        for i, (first, last, position, dept, etype) in enumerate(PEOPLE):
            email = f"{first.lower()}.{last.lower()}@csis.or.id"
            user = User.objects.filter(email=email).first()
            if not user:
                user = User.objects.create_user(
                    email=email, password=DEMO_PASSWORD,
                    first_name=first, last_name=last,
                    phone=f"+6281{200000000 + i}",
                )
                from apps.companies.models import OrganizationMembership
                from apps.hr.signals import ensure_employee_stub
                OrganizationMembership.objects.create(organization=self.org, user=user)
                ensure_employee_stub(user)
            # Employee auto-created by signal (or ensure_employee_stub above for
            # brand-new users) — update the stub with real position/department.
            emp = Employee.objects.get(user=user)
            emp.position = position
            emp.department = dept
            emp.employment_type = etype
            emp.hire_date = self.today - timedelta(days=365 * (i % 6) + 30)
            emp.status = "active"
            emp.salary = Decimal(15_000_000 + (i % 10) * 2_000_000)
            emp.save()
            employees.append(emp)
        self.stdout.write(f"  users + employees: {len(employees)}")
        return employees

    def _seed_directors(self, employees):
        from apps.companies.models import Director

        # First two staff are directors.
        for order, (emp, title) in enumerate([
            (employees[0], "Executive Director"),
            (employees[1], "Deputy Director"),
        ]):
            Director.objects.update_or_create(
                employee=emp,
                defaults={"organization": self.org, "title": title, "order": order, "is_active": True,
                          "bio": f"{emp.user.get_full_name()} serves as {title}."},
            )
        self.stdout.write("  directors: 2")

    # ----------------------------------------------------------------- contacts
    def _seed_contacts(self, companies):
        from apps.contacts.models import Contact

        n = 0
        for name, email, position, abbr in EXTERNAL_CONTACTS:
            Contact.objects.update_or_create(
                email=email,
                defaults=dict(
                    organization=self.org,
                    name=name, position=position, type="individual",
                    company=companies.get(abbr),
                    phone="+62215299000",
                ),
            )
            n += 1
        self.stdout.write(f"  external contacts: {n}")

    # ------------------------------------------------------------------ finance
    def _seed_budget_catalog(self):
        from apps.finance.models import BudgetCategory, BudgetItem

        items = []
        for code, cat_name, item_names in BUDGET_CATALOG:
            category, _ = BudgetCategory.objects.update_or_create(
                organization=self.org, code=code, defaults={"name": cat_name},
            )
            for n, item_name in enumerate(item_names, start=1):
                item, _ = BudgetItem.objects.get_or_create(
                    category=category, name=item_name,
                    defaults={"code": f"{code}.{n}"},
                )
                items.append(item)
        self.stdout.write(f"  budget items: {len(items)}")
        return items

    # ------------------------------------------------------------------ projects
    def _seed_projects(self, employees, companies, budget_items):
        from apps.projects.models import (
            Project, ProjectFund, ProjectFundAllocation, ProjectFinance,
            ProjectTeamMember, ProjectSchedule,
        )

        n_proj = n_fund = n_alloc = n_fin = 0
        for i, (name, status, lead_idx, donor_abbr, amount, currency) in enumerate(PROJECTS):
            lead = employees[lead_idx % len(employees)]
            start = self.today - timedelta(days=180 + i * 20)
            end = start + timedelta(days=540)
            project, _ = Project.objects.update_or_create(
                name=name,
                defaults=dict(
                    organization=self.org,
                    status=status, lead=lead, start_date=start, end_date=end,
                    description=f"{name} — a CSIS research and policy engagement project.",
                    tags=["research", "policy"],
                ),
            )
            n_proj += 1

            fund, _ = ProjectFund.objects.update_or_create(
                project=project, source=companies[donor_abbr].name,
                defaults=dict(
                    organization=self.org,
                    fund_type="grant", donor=companies[donor_abbr],
                    amount=Decimal(amount), currency=currency,
                    status="received", agreement_date=start,
                    received_date=start + timedelta(days=15),
                ),
            )
            n_fund += 1

            # Allocate the fund across a realistic spread of budget items, then
            # book expenses against those SAME items so every expense maps to a
            # budget line. Utilization is varied per line: some under budget,
            # some near the cap, one deliberately over (proves the ratio chart).
            by_code = {bi.code: bi for bi in budget_items}
            # (budget code, unit, qty, freq, unit_cost_share, utilization, expense_label)
            #   unit_cost_share: fraction of `amount` this line's unit cost is sized to.
            #   utilization: spent / allocated for this line.
            ALLOC_PLAN = [
                ("A.1", "person-month", 6, 1, Decimal("0.030"), Decimal("0.92"), "Researcher salaries"),
                ("A.2", "honorarium", 4, 1, Decimal("0.015"), Decimal("0.75"), "Expert honoraria"),
                ("A.3", "person", 6, 1, Decimal("0.008"), Decimal("0.60"), "Staff benefits"),
                ("B.1", "workshop", 3, 1, Decimal("0.025"), Decimal("1.15"), "Workshop costs"),
                ("B.2", "trip", 4, 1, Decimal("0.020"), Decimal("0.85"), "Field research"),
                ("B.3", "trip", 5, 1, Decimal("0.012"), Decimal("1.00"), "Research travel"),
                ("B.4", "report", 2, 1, Decimal("0.018"), Decimal("0.40"), "Publication & printing"),
                ("C.1", "month", 12, 1, Decimal("0.006"), Decimal("0.95"), "Office rent share"),
                ("C.3", "unit", 3, 1, Decimal("0.010"), Decimal("0.50"), "Equipment purchase"),
                ("D.1", "lumpsum", 1, 1, Decimal("0.050"), Decimal("0.70"), "Administrative overhead"),
            ]
            # Vary which lines a project funds so the dataset isn't uniform.
            plan = ALLOC_PLAN[: 6 + (i % 5)]
            amt = Decimal(amount)

            # Grant disbursement(s) — income on the grant ledger.
            ProjectFinance.objects.get_or_create(
                project=project, description=f"Grant disbursement — {fund.source}", type="income",
                defaults=dict(
                    amount=amt, currency=currency, ledger="grant",
                    date=fund.received_date or (start + timedelta(days=15)),
                ),
            )
            n_fin += 1

            for j, (code, unit, qty, freq, cost_share, util, label) in enumerate(plan):
                bi = by_code.get(code)
                if bi is None:
                    continue
                unit_cost = (amt * cost_share).quantize(Decimal("0.01"))
                alloc, created = ProjectFundAllocation.objects.get_or_create(
                    fund=fund, budget_item=bi,
                    defaults=dict(
                        unit=unit, quantity=Decimal(qty), frequency=Decimal(freq),
                        unit_cost=unit_cost, unit_cost_currency=currency,
                    ),
                )
                if created:
                    n_alloc += 1
                # Expense booked against the same budget item, sized to the line's
                # allocated amount × utilization. Split lines >100% into two entries.
                allocated = alloc.amount or (unit_cost * Decimal(qty) * Decimal(freq))
                spent = (allocated * util).quantize(Decimal("0.01"))
                parts = [spent] if util <= 1 else [
                    (allocated).quantize(Decimal("0.01")),
                    (spent - allocated).quantize(Decimal("0.01")),
                ]
                for p, part in enumerate(parts):
                    if part <= 0:
                        continue
                    ProjectFinance.objects.get_or_create(
                        project=project, budget_item=bi,
                        description=f"{label}{' (overrun)' if p else ''}", type="expense",
                        defaults=dict(
                            amount=part, currency=currency, ledger="expense",
                            date=start + timedelta(days=45 + 25 * j + 10 * p),
                        ),
                    )
                    n_fin += 1

            # Team: lead as owner + 2 members.
            ProjectTeamMember.objects.get_or_create(
                project=project, user=lead.user, defaults={"role": "owner"})
            for emp in employees[(i + 1) % len(employees):(i + 3) % len(employees) + 1][:2]:
                ProjectTeamMember.objects.get_or_create(
                    project=project, user=emp.user, defaults={"role": "member"})

            # Schedule milestones.
            for k, phase in enumerate(["Inception", "Field Work", "Reporting"]):
                ProjectSchedule.objects.get_or_create(
                    project=project, title=phase,
                    defaults=dict(
                        start_date=start + timedelta(days=180 * k),
                        end_date=start + timedelta(days=180 * (k + 1)),
                    ),
                )
        self.stdout.write(f"  projects: {n_proj} | funds: {n_fund} | allocations: {n_alloc} | finance: {n_fin}")

    # -------------------------------------------------------------------- events
    def _seed_events(self, admin, employees):
        from apps.events.models import (
            Event, EventTeam, EventSpeaker, EventScheduleItem,
            EventInvitation, EventRegistration,
        )
        from apps.contacts.models import Contact

        speakers_pool = list(Contact.objects.filter(type="individual")[:10])
        n_evt = n_inv = n_reg = 0
        for i, (name, etype, location, status, capacity) in enumerate(EVENTS):
            start = timezone.now() + timedelta(days=(i - 5) * 14)
            event, _ = Event.objects.update_or_create(
                name=name,
                defaults=dict(
                    organization=self.org,
                    event_type=etype, location=location, status=status,
                    capacity=capacity, organizer=admin,
                    description=f"{name} hosted by CSIS.",
                    start_date=start, end_date=start + timedelta(hours=6),
                    tags=[etype],
                ),
            )
            n_evt += 1

            # Team: 2 staff.
            for emp in employees[i % len(employees):i % len(employees) + 2]:
                EventTeam.objects.get_or_create(
                    event=event, user=emp.user,
                    defaults={"role": "coordinator", "is_lead": False})

            # Speakers from contact pool.
            for s, contact in enumerate(speakers_pool[i % 5:i % 5 + 2]):
                EventSpeaker.objects.get_or_create(
                    event=event, name=contact.name,
                    defaults=dict(
                        contact=contact, title=contact.position or "Speaker",
                        organization=contact.company.name if contact.company_id else "",
                        session_type="keynote" if s == 0 else "panel",
                        session_title=f"Session {s + 1}",
                    ),
                )

            # Schedule items.
            for k, (title, stype) in enumerate([
                ("Opening Remarks", "ceremony"),
                ("Keynote Address", "keynote"),
                ("Panel Discussion", "panel"),
            ]):
                EventScheduleItem.objects.get_or_create(
                    event=event, title=title,
                    defaults=dict(
                        session_type=stype,
                        start_time=(timezone.datetime(2026, 1, 1, 9 + k * 2)).time(),
                        end_time=(timezone.datetime(2026, 1, 1, 10 + k * 2)).time(),
                    ),
                )

            # Invitations + registrations from external contacts.
            for c in Contact.objects.exclude(email="")[i % 8:i % 8 + 5]:
                _, created = EventInvitation.objects.get_or_create(
                    event=event, email=c.email,
                    defaults=dict(name=c.name, contact=c,
                                  status="accepted" if n_inv % 2 else "pending",
                                  invited_by=admin),
                )
                if created:
                    n_inv += 1
                _, rcreated = EventRegistration.objects.get_or_create(
                    event=event, email=c.email,
                    defaults=dict(name=c.name, contact=c, phone=c.phone,
                                  company=c.company.name if c.company_id else "",
                                  status="registered"),
                )
                if rcreated:
                    n_reg += 1
        self.stdout.write(f"  events: {n_evt} | invitations: {n_inv} | registrations: {n_reg}")

    # ------------------------------------------------------------------ summary
    def _summary(self):
        from apps.hr.models import Employee
        from apps.contacts.models import Contact
        from apps.companies.models import Company
        from apps.projects.models import Project, ProjectFund
        from apps.events.models import Event

        self.stdout.write(self.style.MIGRATE_HEADING("\n═══ SEED DEMO SUMMARY ═══"))
        rows = [
            ("Users", User.objects.count()),
            ("Employees", Employee.objects.count()),
            ("Contacts", Contact.objects.count()),
            ("Companies (orgs+donors)", Company.objects.count()),
            ("  of which donors", Company.objects.filter(company_type="donor").count()),
            ("Projects", Project.objects.count()),
            ("Project funds", ProjectFund.objects.count()),
            ("Events", Event.objects.count()),
        ]
        for label, count in rows:
            self.stdout.write(f"  {label:<26} {count}")
        self.stdout.write(self.style.SUCCESS("\nDone. Login: admin@gmail.com / 687654"))
