"""test_homework.py - tests for the homework app.

A test is a small program that checks a bigger program. You run it with
one word, and it tells you the truth about your code.

    pip3 install pytest
    python3 -m pytest -v

Every test here uses its OWN empty database, made fresh in a temporary
folder. Tests must never touch your real homework.db, and they must never
depend on each other.
"""
import sqlite3

import pytest

import homework


# ---------------------------------------------------------------- fixture
@pytest.fixture
def db(tmp_path, monkeypatch):
    """Give each test a clean, empty database of its own.

    tmp_path is a new empty folder, made by pytest, thrown away after.
    monkeypatch points the app at it, just for this one test.
    """
    monkeypatch.setattr(homework, "DB_PATH", tmp_path / "test.db")
    conn = homework.open_db()
    yield conn                 # the test runs here
    conn.close()               # and this happens afterwards, always


class Args:
    """A stand-in for what argparse would hand to the functions."""
    def __init__(self, **kwargs):
        self.student = None
        self.title = None
        self.due = None
        self.job_id = None
        for key, value in kwargs.items():
            setattr(self, key, value)


def count_rows(conn):
    return conn.execute("SELECT COUNT(*) FROM jobs").fetchone()[0]


# ---------------------------------------------------------------- tests
def test_new_database_is_empty(db):
    assert count_rows(db) == 0


def test_add_puts_one_row_in(db):
    homework.add(db, Args(student="Ploy", title="Unit 3", due=None))
    assert count_rows(db) == 1


def test_added_row_keeps_what_we_gave_it(db):
    homework.add(db, Args(student="Ploy", title="Unit 3", due="2026-09-01"))
    row = db.execute("SELECT student, title, due, done FROM jobs").fetchone()
    assert row == ("Ploy", "Unit 3", "2026-09-01", 0)


def test_a_new_job_is_not_finished(db):
    homework.add(db, Args(student="Ploy", title="Unit 3", due=None))
    done = db.execute("SELECT done FROM jobs").fetchone()[0]
    assert done == 0


# A name with an apostrophe used to break programs like this one.
# This test is here so it can never come back.
@pytest.mark.parametrize("name", [
    "Ploy",
    "O'Brien",
    "D'Angelo",
    'Say "hello"',
    "น้องหมี",          # Thai
    "张伟",                                        # Chinese
    "Robert'); DROP TABLE jobs;--",                        # the classic attack
])
def test_any_name_survives(db, name):
    homework.add(db, Args(student=name, title="Read page 40", due=None))
    saved = db.execute("SELECT student FROM jobs").fetchone()[0]
    assert saved == name
    assert count_rows(db) == 1          # the table is still standing


def test_done_marks_the_right_job(db):
    homework.add(db, Args(student="Ploy", title="First", due=None))
    homework.add(db, Args(student="Ploy", title="Second", due=None))

    homework.finish(db, Args(job_id=1))

    rows = db.execute("SELECT id, done FROM jobs ORDER BY id").fetchall()
    assert rows == [(1, 1), (2, 0)]     # only job 1 changed


def test_done_on_a_missing_job_returns_an_error_code(db):
    result = homework.finish(db, Args(job_id=99))
    assert result == 1                  # non-zero means "something was wrong"


def test_done_twice_is_harmless(db):
    homework.add(db, Args(student="Ploy", title="First", due=None))
    homework.finish(db, Args(job_id=1))
    assert homework.finish(db, Args(job_id=1)) == 0


def test_report_counts_each_student(db, capsys):
    homework.add(db, Args(student="Ploy", title="a", due=None))
    homework.add(db, Args(student="Ploy", title="b", due=None))
    homework.add(db, Args(student="Nok", title="c", due=None))
    homework.finish(db, Args(job_id=1))

    homework.report(db, Args())
    printed = capsys.readouterr().out

    assert "Ploy" in printed
    assert "Nok" in printed


def test_the_data_is_really_on_disk(db, tmp_path):
    """Close everything, open it again, and the row must still be there."""
    homework.add(db, Args(student="Ploy", title="Unit 3", due=None))
    db.close()

    fresh = sqlite3.connect(tmp_path / "test.db")
    assert fresh.execute("SELECT COUNT(*) FROM jobs").fetchone()[0] == 1
    fresh.close()
