"""Create lab.db, a SQLite copy of the core schema with sample data.

The classes come from planner.core, so run this with the planner venv active.
The courses are real; every person is generated by Faker.
"""

import random
from datetime import UTC, date, datetime, timedelta
from decimal import Decimal

from faker import Faker
from planner.core import (
    CatalogYear,
    Course,
    Faculty,
    LoginEvent,
    Person,
    Student,
    Subject,
    Term,
)
from sqlmodel import Session, SQLModel, create_engine

engine = create_engine("sqlite:///lab.db")

fake = Faker()
Faker.seed(374)
random.seed(374)

SUBJECTS = [
    ("CS", "Computer Science"),
    ("IT", "Information Technology"),
    ("MATH", "Mathematics"),
]

# (subject, number, title, credits, is_active), from catalog.jmu.edu.
# The slides and the lab use course_id 13 for CS 374, so keep it 13th.
COURSES = [
    ("CS", "101", "Introduction to Computer Science", "3.0", True),
    ("CS", "149", "Introduction to Programming", "3.0", True),
    ("CS", "159", "Advanced Programming", "3.0", True),
    ("CS", "227", "Discrete Structures I", "3.0", True),
    ("CS", "240", "Algorithms and Data Structures", "3.0", True),
    ("CS", "261", "Computer Systems I", "3.0", True),
    ("CS", "327", "Discrete Structures II", "3.0", True),
    ("CS", "332", "Technical Communication for Computer Science", "3.0", False),
    ("CS", "343", "Application Development", "3.0", True),
    ("CS", "345", "Software Engineering", "3.0", True),
    ("CS", "347", "Web Development", "3.0", True),
    ("CS", "361", "Computer Systems II", "3.0", True),
    ("CS", "374", "Database Systems", "3.0", True),
    ("CS", "430", "Programming Languages", "3.0", True),
    ("CS", "442", "Logic in Computer Science", "3.0", False),
    ("CS", "444", "Artificial Intelligence", "3.0", True),
    ("CS", "445", "Machine Learning", "3.0", True),
    ("CS", "450", "Operating Systems", "3.0", True),
    ("CS", "452", "Design and Analysis of Algorithms", "3.0", True),
    ("CS", "457", "Information Security", "3.0", True),
    ("CS", "462", "Network Applications Development", "3.0", False),
    ("CS", "499A", "Honors", "1.0", True),
    ("CS", "499B", "Senior Honors Thesis", "3.0", True),
    ("CS", "499C", "Senior Honors Thesis", "2.0", True),
    ("IT", "101", "Introduction to Information Technology", "3.0", True),
    ("IT", "160", "Data Structures and Advanced Programming", "3.0", True),
    ("IT", "201", "Computational Structures and Logic", "3.0", True),
    ("IT", "240", "Database Administration", "3.0", True),
    ("IT", "301", "Web Technologies", "3.0", True),
    ("MATH", "220", "Elementary Statistics", "3.0", True),
    ("MATH", "227", "Discrete Structures I", "3.0", True),
    ("MATH", "231", "Calculus with Functions I", "3.0", True),
    ("MATH", "235", "Calculus I", "4.0", True),
    ("MATH", "236", "Calculus II", "4.0", True),
    ("MATH", "237", "Calculus III", "4.0", True),
    ("MATH", "238", "Linear Algebra with Differential Equations", "4.0", True),
    ("MATH", "245", "Discrete Mathematics", "3.0", True),
]

# Fall 2022 through Spring 2028
YEARS = range(2022, 2028)


def make_terms() -> list[Term]:
    terms = []
    for year in YEARS:
        fall = 1000 + (year % 100) * 10 + 8
        spring = 1000 + (year % 100 + 1) * 10 + 1
        terms.append(Term(term_code=fall, year=year, season="Fall",
                          start_date=date(year, 8, 24), end_date=date(year, 12, 11)))
        terms.append(Term(term_code=spring, year=year + 1, season="Spring",
                          start_date=date(year + 1, 1, 11), end_date=date(year + 1, 4, 30)))
    return terms


def make_catalog_years() -> list[CatalogYear]:
    return [
        CatalogYear(catalog_year=year, start_term=1000 + (year % 100) * 10 + 8,
                    description=f"{year}-{(year + 1) % 100:02d} Undergraduate Catalog")
        for year in range(2022, 2027)
    ]


def make_person() -> Person:
    return Person(
        first_name=fake.first_name(),
        last_name=fake.last_name(),
        preferred_name=random.choice([None] * 9 + [fake.first_name()]),
        email=fake.unique.user_name() + "@example.edu",
        is_active=random.random() < 0.95,
        created_at=fake.date_time_between(
            datetime(2022, 6, 1, tzinfo=UTC), datetime(2026, 9, 1, tzinfo=UTC), UTC
        ),
    )


def make_student(person: Person, number: int) -> Student:
    catalog = random.choice([2022, 2023, 2024, 2025, 2026])
    admit = 1000 + (catalog % 100) * 10 + 8
    status = random.choices(
        ["active", "graduated", "inactive", "left_major"], weights=[70, 15, 8, 7]
    )[0]
    grad = 1000 + (catalog % 100 + 4) * 10 + 1
    return Student(
        person=person,
        student_number=str(number),
        admit_term=admit,
        catalog_year=catalog,
        status=status,
        expected_grad_term=grad if grad <= 1281 and random.random() < 0.8 else None,
    )


def make_faculty(person: Person, program: str) -> Faculty:
    return Faculty(
        person=person,
        program=program,
        rank=random.choice(["Lecturer", "Assistant Professor", "Associate Professor",
                            "Professor"]),
        office=random.choice(["King", "ISAT"]) + f" {random.randint(1, 3)}{random.randint(10, 60)}",
        is_advisor=random.random() < 0.7,
    )


def make_logins(people: list[Person]) -> list[LoginEvent]:
    events = []
    start = datetime(2026, 8, 24, tzinfo=UTC)
    for _ in range(600):
        person = random.choice(people)
        occurred = start + timedelta(minutes=random.randint(0, 60 * 24 * 50))
        if random.random() < 0.05:
            # someone typed an address that matches nobody
            events.append(LoginEvent(email_attempted=fake.email(), occurred_at=occurred,
                                     succeeded=False, ip_address=fake.ipv4()))
            continue
        events.append(LoginEvent(
            person_id=person.person_id,
            email_attempted=person.email,
            occurred_at=occurred,
            succeeded=random.random() < 0.9,
            ip_address=fake.ipv4(),
            user_agent=fake.user_agent(),
        ))
    return events


if __name__ == "__main__":
    SQLModel.metadata.drop_all(engine)
    SQLModel.metadata.create_all(engine)

    with Session(engine) as session:
        session.add_all(make_terms())
        session.add_all(make_catalog_years())
        session.add_all(Subject(code=c, name=n) for c, n in SUBJECTS)
        session.add_all(
            Course(subject_code=s, number=n, title=t, credits=Decimal(c), is_active=a)
            for s, n, t, c, a in COURSES
        )

        people = [make_person() for _ in range(80)]
        for i, person in enumerate(people[:60]):
            session.add(make_student(person, 108000001 + i))
        for i, person in enumerate(people[58:70]):
            # people 58 and 59 are both students and faculty (graduate TAs)
            session.add(make_faculty(person, "IT" if i % 4 == 0 else "CS"))
        # the remaining ten people are staff: neither students nor faculty
        session.add_all(people)
        session.commit()

        session.add_all(make_logins(people))
        session.commit()

    print("Created lab.db")
