"""
Seed realistic demo data (homework, announcements, class tests, attendance)
into a single school's database, scoped to its current academic year.

Every subject-teacher assignment (Teacher x Subject x AcademicClass, i.e. "who
teaches what to which class this year") is read from SubjectTeacher — nothing
is invented; homework and class tests are created only for classes/subjects a
teacher actually teaches, exactly matching what the teacher app already shows
them.

WHAT THIS DOES NOT TOUCH: exams (formal Exam/ExamSubject/results) and fee
records (ClassFeeStructure/FeePayment/etc.) are intentionally left alone —
those carry grading-configuration and financial-record risk that's out of
scope for a demo-data seeder. Everything this command creates is clearly
demo content (see the CONTENT bank below) and can be identified/cleaned up
later by its is_active flag or title text if needed.

Usage:
    python manage.py seed_demo_data --school <slug-or-id>
    python manage.py seed_demo_data --school <slug-or-id> --dry-run
    python manage.py seed_demo_data --school <slug-or-id> --modules homework,attendance
    python manage.py seed_demo_data --school <slug-or-id> --yes

Safe to re-run: every module skips a class/subject/day combo that already
has data, so running it twice never duplicates.
"""

import random
from datetime import timedelta

from django.core.management.base import BaseCommand, CommandError
from django.db import connections, transaction
from django.utils import timezone


class Command(BaseCommand):
    help = "Seed demo homework, announcements, class tests, and attendance for a school's current academic year."

    ALL_MODULES = ["homework", "announcements", "classtests", "attendance"]

    def add_arguments(self, parser):
        parser.add_argument(
            "--school", required=True,
            help="School slug (e.g. 'greenwood-high') or numeric School id (master DB).",
        )
        parser.add_argument(
            "--academic-year",
            help="Academic year id or exact name to target. Default: the school's currently active year.",
        )
        parser.add_argument(
            "--modules",
            default=",".join(self.ALL_MODULES),
            help=f"Comma-separated subset of: {', '.join(self.ALL_MODULES)}. Default: all.",
        )
        parser.add_argument(
            "--attendance-days", type=int, default=10,
            help="How many past working days to backfill attendance for (ending yesterday — today is left open for a live demo). Default: 10.",
        )
        parser.add_argument(
            "--classes",
            help="Optional comma-separated AcademicClass ids to restrict all modules to (default: every class in the target year).",
        )
        parser.add_argument(
            "--dry-run", action="store_true",
            help="Only print what would be created — writes nothing.",
        )
        parser.add_argument(
            "--yes", action="store_true",
            help="Skip the confirmation prompt before writing.",
        )

    # ------------------------------------------------------------------
    # Entry point
    # ------------------------------------------------------------------

    def handle(self, *args, **options):
        self.dry_run = options["dry_run"]
        self.rand = random.Random(42)  # deterministic-ish, still varied

        school = self._resolve_school(options["school"])
        self._point_school_connection(school)

        # Imports deferred until AFTER the school DB connection is pointed
        # correctly — these modules' managers must not run at import time.
        from academics.models import AcademicYear, AcademicClass, SubjectTeacher

        target_year = self._resolve_academic_year(AcademicYear, options["academic_year"])

        class_filter = {}
        if options["classes"]:
            class_filter["id__in"] = [int(x) for x in options["classes"].split(",") if x.strip()]

        classes = list(
            AcademicClass.objects.filter(
                academic_year=target_year, is_active=True, **class_filter
            ).select_related("standard", "section")
        )
        if not classes:
            raise CommandError(
                f"No active classes found for academic year '{target_year.name}'. Nothing to seed."
            )

        subject_teachers = list(
            SubjectTeacher.objects.filter(
                academic_class__in=classes, is_active=True
            ).select_related("teacher", "subject", "academic_class__standard", "academic_class__section")
        )

        requested_modules = [m.strip() for m in options["modules"].split(",") if m.strip()]
        for m in requested_modules:
            if m not in self.ALL_MODULES:
                raise CommandError(f"Unknown module '{m}'. Choose from: {', '.join(self.ALL_MODULES)}")

        self.stdout.write(self.style.MIGRATE_HEADING(
            f"\nSchool: {school.name} ({school.slug})\n"
            f"Academic year: {target_year.name} ({target_year.start_date} to {target_year.end_date})\n"
            f"Classes in scope: {len(classes)}\n"
            f"Subject-teacher assignments in scope: {len(subject_teachers)}\n"
            f"Modules: {', '.join(requested_modules)}\n"
            f"Mode: {'DRY RUN (no writes)' if self.dry_run else 'LIVE — will write to the database'}\n"
        ))

        if not subject_teachers:
            self.stdout.write(self.style.WARNING(
                "No active SubjectTeacher assignments found for these classes/year — "
                "homework and class tests need at least one to know who teaches what."
            ))

        if not self.dry_run and not options["yes"]:
            confirm = input("Proceed and write this data now? [y/N]: ").strip().lower()
            if confirm != "y":
                self.stdout.write(self.style.WARNING("Aborted — nothing was written."))
                return

        # Each module runs in its own try/except so one module's failure
        # (a data edge case in one class, say) can't take the others down —
        # important when this is run live, once, before a demo.
        module_runners = {
            "homework": lambda: self._seed_homework(subject_teachers),
            "announcements": lambda: self._seed_announcements(classes),
            "classtests": lambda: self._seed_classtests(subject_teachers, target_year),
            "attendance": lambda: self._seed_attendance(classes, options["attendance_days"]),
        }

        totals = {}
        failures = []
        for module in requested_modules:
            try:
                totals[module] = module_runners[module]()
            except Exception as e:
                failures.append(module)
                self.stderr.write(self.style.ERROR(f"[{module}] FAILED: {e}"))
                import traceback
                self.stderr.write(traceback.format_exc())

        self.stdout.write(self.style.SUCCESS("\n" + ("Dry-run summary" if self.dry_run else "Done") + ":"))
        for module, count in totals.items():
            self.stdout.write(f"  {module}: {count}")
        if failures:
            # homework/announcements/classtests each run in one transaction,
            # so a failure there rolls back cleanly. attendance commits
            # per-class, so a mid-run failure may leave earlier classes'
            # sessions already written — check the counts above/logs.
            self.stdout.write(self.style.ERROR(f"  Failed modules (see error above — check what was partially written): {', '.join(failures)}"))

    # ------------------------------------------------------------------
    # School / year resolution
    # ------------------------------------------------------------------

    def _resolve_school(self, identifier):
        from master_admin.models import School

        qs = School.objects.all()
        school = None
        if identifier.isdigit():
            school = qs.filter(id=int(identifier)).first()
        if not school:
            school = qs.filter(slug=identifier).first()
        if not school:
            raise CommandError(f"No school found matching '{identifier}' (tried id and slug).")
        return school

    def _point_school_connection(self, school):
        """Same trick as SchoolContextMiddleware — point the shared 'school'
        DB alias at this school's actual database before any school-app
        query runs."""
        connections["school"].settings_dict.update({
            "NAME": school.db_name,
            "USER": school.db_user,
            "PASSWORD": school.db_password,
            "HOST": school.db_host,
            "PORT": school.db_port,
        })
        connections["school"].close()

    def _resolve_academic_year(self, AcademicYear, override):
        if override:
            if override.isdigit():
                year = AcademicYear.objects.filter(id=int(override)).first()
            else:
                year = AcademicYear.objects.filter(name=override).first()
            if not year:
                raise CommandError(f"Academic year '{override}' not found.")
            return year

        today = timezone.now().date()
        year = AcademicYear.objects.filter(
            is_active=True, start_date__lte=today, end_date__gte=today
        ).first()
        if not year:
            year = AcademicYear.objects.filter(is_active=True).first()
        if not year:
            raise CommandError(
                "No active academic year found for this school. Pass --academic-year explicitly."
            )
        return year

    # ------------------------------------------------------------------
    # Content bank
    # ------------------------------------------------------------------

    def _homework_content(self, subject_name):
        name = (subject_name or "").lower()
        if any(k in name for k in ("math", "algebra", "geometry")):
            bank = [
                ("Practice Worksheet: Chapter Exercises", "Complete the exercises assigned in class and show your working for every step.",
                 ["Solve the five word problems from the textbook exercise.",
                  "Show step-by-step working for each equation.",
                  "Attempt the bonus challenge question."]),
            ]
        elif any(k in name for k in ("english", "tamil", "hindi", "language")):
            bank = [
                (f"{subject_name} Reading & Writing Assignment", "Read the assigned chapter and answer the comprehension questions in your notebook.",
                 ["Write a short paragraph summarizing the chapter.",
                  "Answer the comprehension questions at the end of the chapter.",
                  "Find and note down five new vocabulary words with meanings."]),
            ]
        elif "science" in name or "physics" in name or "chemistry" in name or "biology" in name:
            bank = [
                (f"{subject_name} Chapter Review", "Review the chapter covered in class and complete the questions below.",
                 ["Explain the main concept covered in today's class in your own words.",
                  "Answer the review questions from the textbook.",
                  "Draw and label the diagram discussed in class."]),
            ]
        elif "computer" in name or "informatics" in name:
            bank = [
                ("Practice Exercise", "Complete the practice exercise covered in the lab session.",
                 ["Write the program/steps discussed in class.",
                  "Answer the short-answer questions on today's topic.",
                  "Note down one real-world use case for what we learned today."]),
            ]
        elif "social" in name or "evs" in name or "history" in name or "geography" in name:
            bank = [
                (f"{subject_name} Notes & Questions", "Go through today's topic and answer the questions below.",
                 ["Write short notes on the topic covered in class.",
                  "Answer the questions given at the end of the lesson.",
                  "Mark the relevant locations/dates discussed in class."]),
            ]
        else:
            bank = [
                (f"{subject_name} Homework", "Complete the assignment based on today's class discussion.",
                 ["Answer the questions covered in today's class.",
                  "Revise the notes taken during the lesson.",
                  "Complete the practice exercise given by the teacher."]),
            ]
        return self.rand.choice(bank)

    def _announcement_bank(self):
        return [
            ("PTA Meeting Notice", "A Parent-Teacher meeting is scheduled. Please make it convenient to attend and discuss your child's progress.", "medium"),
            ("Upcoming Holiday", "Please note the school will remain closed for the upcoming holiday. Regular classes resume the following working day.", "low"),
            ("Sports Day Announcement", "Our annual Sports Day is coming up! Students are encouraged to participate in the events being organized.", "medium"),
            ("Fee Payment Reminder", "This is a gentle reminder to complete pending fee payments for this term at your earliest convenience.", "high"),
        ]

    # ------------------------------------------------------------------
    # Module: homework
    # ------------------------------------------------------------------

    def _seed_homework(self, subject_teachers):
        from tasks.models import ClassTask, TaskItem, TaskType

        if self.dry_run:
            count = 0
            for st in subject_teachers:
                exists = ClassTask.objects.filter(
                    academic_class=st.academic_class, subject=st.subject, posted_by=st.teacher
                ).exists()
                if not exists:
                    count += 1
            self.stdout.write(f"[homework] would create {count} homework task(s)")
            return count

        hw_type, _ = TaskType.objects.get_or_create(
            code="HOMEWORK", defaults={"name": "Homework"}
        )

        created = 0
        today = timezone.now().date()
        with transaction.atomic(using="school"):
            for st in subject_teachers:
                already = ClassTask.objects.filter(
                    academic_class=st.academic_class, subject=st.subject, posted_by=st.teacher
                ).exists()
                if already:
                    continue

                title, description, questions = self._homework_content(st.subject.name)
                due_offset = self.rand.choice([-2, -1, 1, 2, 3, 4, 5, 7])
                due_date = today + timedelta(days=due_offset)
                if due_date.weekday() >= 5:  # push weekend due dates to Monday
                    due_date += timedelta(days=7 - due_date.weekday())

                per_q_marks = self.rand.choice([5, 10])
                task = ClassTask.objects.create(
                    academic_class=st.academic_class,
                    subject=st.subject,
                    task_type=hw_type,
                    posted_by=st.teacher,
                    title=title,
                    description=description,
                    due_date=due_date,
                    total_marks=per_q_marks * len(questions),
                    is_published=True,
                    is_active=True,
                )
                TaskItem.objects.bulk_create([
                    TaskItem(
                        class_task=task, question_text=q, marks=per_q_marks,
                        order=i + 1, is_active=True,
                    )
                    for i, q in enumerate(questions)
                ])
                created += 1

        self.stdout.write(self.style.SUCCESS(f"[homework] created {created} homework task(s)"))
        return created

    # ------------------------------------------------------------------
    # Module: announcements
    # ------------------------------------------------------------------

    def _seed_announcements(self, classes):
        from announcements.models import CommonAnnouncement, ClassAnnouncement, AnnouncementType

        bank = self._announcement_bank()

        if self.dry_run:
            common_missing = sum(
                1 for title, _, _ in bank
                if not CommonAnnouncement.objects.filter(title=title).exists()
            )
            class_missing = sum(
                1 for ac in classes
                if not ClassAnnouncement.objects.filter(academic_class=ac).exists()
            )
            self.stdout.write(
                f"[announcements] would create {common_missing} common + {class_missing} class announcement(s)"
            )
            return common_missing + class_missing

        ann_type, _ = AnnouncementType.objects.get_or_create(
            code="GENERAL", defaults={"name": "General"}
        )
        today = timezone.now().date()
        created = 0

        with transaction.atomic(using="school"):
            for title, message, priority in bank:
                _, is_new = CommonAnnouncement.objects.get_or_create(
                    title=title,
                    defaults={
                        "announcement_type": ann_type,
                        "message": message,
                        "priority": priority,
                        "expire_on": today + timedelta(days=30),
                        "from_role": "school_admin",
                        "is_active": True,
                    },
                )
                if is_new:
                    created += 1

            class_bank = [
                ("Class Notice", "Please ensure your child brings all required textbooks and notebooks daily.", "low"),
            ]
            for ac in classes:
                if ClassAnnouncement.objects.filter(academic_class=ac).exists():
                    continue
                title, message, priority = self.rand.choice(class_bank)
                ClassAnnouncement.objects.create(
                    announcement_type=ann_type,
                    academic_class=ac,
                    title=title,
                    message=message,
                    priority=priority,
                    expire_on=today + timedelta(days=30),
                    from_role="school_admin",
                    is_active=True,
                )
                created += 1

        self.stdout.write(self.style.SUCCESS(f"[announcements] created {created} announcement(s)"))
        return created

    # ------------------------------------------------------------------
    # Module: class tests
    # ------------------------------------------------------------------

    def _seed_classtests(self, subject_teachers, target_year):
        from academics.models import StudentEnrollment
        from exam.models import ClassTest, ClassTestStudent

        if self.dry_run:
            count = sum(1 for st in subject_teachers if not ClassTest.objects.filter(subject_teacher=st).exists())
            self.stdout.write(f"[classtests] would create {count} class test(s)")
            return count

        today = timezone.now().date()
        test_date = today - timedelta(days=3)
        if test_date.weekday() >= 5:
            test_date -= timedelta(days=test_date.weekday() - 4)

        created = 0
        with transaction.atomic(using="school"):
            for st in subject_teachers:
                if ClassTest.objects.filter(subject_teacher=st).exists():
                    continue

                max_marks = 20
                passing_marks = 8
                test = ClassTest.objects.create(
                    title=f"{st.subject.name} Mini Test",
                    test_type="MINI_TEST",
                    academic_year=target_year,
                    academic_class=st.academic_class,
                    subject=st.subject,
                    subject_teacher=st,
                    test_date=test_date,
                    period_number=1,
                    max_marks=max_marks,
                    passing_marks=passing_marks,
                    duration_minutes=30,
                    description=f"Mini class test covering recent topics in {st.subject.name}.",
                    status="MARKS_ENTERED",
                    created_by=st.teacher,
                )

                enrollments = StudentEnrollment.objects.filter(
                    academic_class=st.academic_class, is_active=True
                )
                now = timezone.now()
                rows = []
                for enrollment in enrollments:
                    roll = self.rand.random()
                    if roll < 0.05:
                        rows.append(ClassTestStudent(
                            class_test=test, student_enrollment=enrollment,
                            is_absent=True, marks_entered_by=st.teacher, marks_entered_at=now,
                        ))
                    else:
                        marks = self.rand.randint(6, max_marks) if roll > 0.15 else self.rand.randint(2, 7)
                        rows.append(ClassTestStudent(
                            class_test=test, student_enrollment=enrollment,
                            marks_obtained=marks, marks_entered_by=st.teacher, marks_entered_at=now,
                        ))
                ClassTestStudent.objects.bulk_create(rows)
                created += 1

        self.stdout.write(self.style.SUCCESS(f"[classtests] created {created} class test(s)"))
        return created

    # ------------------------------------------------------------------
    # Module: attendance
    # ------------------------------------------------------------------

    def _is_school_open(self, WeekDay, weekday_cache, d):
        code = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"][d.weekday()]
        if code not in weekday_cache:
            wd = WeekDay.objects.filter(day_code=code).first()
            weekday_cache[code] = wd.is_open if wd else (d.weekday() < 5)
        return weekday_cache[code]

    def _seed_attendance(self, classes, num_days):
        from academics.models import StudentEnrollment
        from attendance.models import AttendanceSession, StudentAttendance, AttendanceSummary
        from schedules.models import WeekDay

        today = timezone.now().date()
        weekday_cache = {}
        candidate_dates = []
        d = today - timedelta(days=1)
        while len(candidate_dates) < num_days and (today - d).days < num_days * 3 + 14:
            if self._is_school_open(WeekDay, weekday_cache, d):
                candidate_dates.append(d)
            d -= timedelta(days=1)

        if self.dry_run:
            planned = 0
            for ac in classes:
                for d in candidate_dates:
                    exists = AttendanceSession.objects.filter(
                        academic_class=ac, date=d, period_number=None, session_type="FULL_DAY"
                    ).exists()
                    if not exists:
                        planned += 1
            self.stdout.write(
                f"[attendance] would create {planned} session(s) across {len(classes)} class(es) "
                f"x {len(candidate_dates)} working day(s) ending {today - timedelta(days=1)}"
            )
            return planned

        sessions_created = 0
        touched_month_years = set()

        for ac in classes:
            taken_by = ac.class_teacher
            enrollments = list(StudentEnrollment.objects.filter(academic_class=ac, is_active=True))
            if not enrollments:
                continue

            with transaction.atomic(using="school"):
                for d in candidate_dates:
                    if AttendanceSession.objects.filter(
                        academic_class=ac, date=d, period_number=None, session_type="FULL_DAY"
                    ).exists():
                        continue

                    session = AttendanceSession.objects.create(
                        academic_class=ac, date=d, session_type="FULL_DAY",
                        status="SUBMITTED", taken_by=taken_by,
                    )

                    rows = []
                    for enrollment in enrollments:
                        roll = self.rand.random()
                        if roll < 0.03:
                            status_val, late_minutes = "ABSENT", None
                        elif roll < 0.08:
                            status_val, late_minutes = "LATE", self.rand.choice([5, 10, 15])
                        elif roll < 0.10:
                            status_val, late_minutes = "LEAVE", None
                        else:
                            status_val, late_minutes = "PRESENT", None
                        rows.append(StudentAttendance(
                            session=session, enrollment=enrollment, student=enrollment.student,
                            status=status_val, late_minutes=late_minutes, marked_by=taken_by,
                        ))
                    StudentAttendance.objects.bulk_create(rows)
                    session.update_counts()
                    sessions_created += 1
                    touched_month_years.add((ac.id, d.month, d.year))

            # Recompute the monthly rollup once per (class, month, year) touched.
            for (class_id, month, year) in {t for t in touched_month_years if t[0] == ac.id}:
                for enrollment in enrollments:
                    summary, _ = AttendanceSummary.objects.get_or_create(
                        enrollment=enrollment, month=month, year=year,
                        defaults={"academic_class": ac, "student": enrollment.student},
                    )
                    summary.compute()

        self.stdout.write(self.style.SUCCESS(f"[attendance] created {sessions_created} session(s)"))
        return sessions_created
