import csv
import sqlite3
import tempfile
import unittest
from pathlib import Path
from repository import Library


class LibraryTest(unittest.TestCase):
    def setUp(self):
        self.folder = tempfile.TemporaryDirectory()
        self.addCleanup(self.folder.cleanup)
        self.library = Library(Path(self.folder.name) / "library.db")

    def test_stock_and_return_once(self):
        ident = self.library.add_book("Buku A", 1)
        loan = self.library.borrow(ident, "Siswa A")
        with self.assertRaises(ValueError):
            self.library.borrow(ident, "Siswa B")
        self.assertEqual(len(self.library.loans()), 1)
        self.assertEqual(self.library.books()[0][2], 0)
        self.library.give_back(loan)
        with self.assertRaises(ValueError):
            self.library.give_back(loan)
        self.assertEqual(self.library.books()[0][2], 1)
        reopened = Library(self.library.path)
        self.assertEqual(reopened.loans()[0][3], 1)

    def test_invalid_inputs_do_not_write(self):
        for title, stock in [(" ", 1), ("A", -1), ("A", True)]:
            with self.assertRaises(ValueError):
                self.library.add_book(title, stock)
        self.assertEqual(self.library.books(), [])
        with self.assertRaises(ValueError):
            self.library.borrow(999, "Siswa A")
        with self.assertRaises(ValueError):
            self.library.give_back(999)

    def test_parameters_and_identity(self):
        first = self.library.add_book("Reader's book", 1)
        second = self.library.add_book("Lain", 2)
        found = self.library.books("Reader's")
        self.library.borrow(found[0][0], "Siswa O'Neil")
        self.assertEqual(found[0][0], first)
        self.assertEqual(self.library.books("Lain"), [(second, "Lain", 2)])
        self.assertEqual(self.library.books("' OR 1=1 --"), [])

    def test_transaction_rolls_back_on_insert_failure(self):
        ident = self.library.add_book("Buku", 1)
        with self.library.connection() as con:
            con.execute("CREATE TRIGGER fail_loan BEFORE INSERT ON loans BEGIN SELECT RAISE(ABORT,'latihan gagal'); END")
        with self.assertRaises(sqlite3.IntegrityError):
            self.library.borrow(ident, "Siswa A")
        self.assertEqual(self.library.books()[0][2], 1)
        self.assertEqual(self.library.loans(), [])

    def test_csv_export_preserves_database(self):
        ident = self.library.add_book("Buku", 1)
        self.library.borrow(ident, "Siswa A")
        path = Path(self.folder.name) / "report.csv"
        self.library.export(path)
        with path.open(newline="", encoding="utf-8") as stream:
            self.assertEqual(len(list(csv.reader(stream))), 2)
        with self.assertRaises(ValueError):
            self.library.export(self.library.path)
        self.assertEqual(len(self.library.loans()), 1)


class GuiSmokeTest(unittest.TestCase):
    def test_callbacks_preserve_ids(self):
        import tkinter as tk
        import app
        with tempfile.TemporaryDirectory() as folder:
            root = tk.Tk()
            root.withdraw()
            try:
                library = Library(Path(folder) / "gui.db")
                library.add_book("Lain", 5)
                ident = library.add_book("Sains", 1)
                view = app.build_window(root, library)
                view["query"].set("Sains")
                view["refresh"]()
                view["books"].selection_set(str(ident))
                view["borrower"].set("Siswa A")
                view["buttons"]["Pinjam"].invoke()
                loan = library.loans()[0][0]
                view["loans"].selection_set(str(loan))
                view["buttons"]["Kembali"].invoke()
                root.update_idletasks()
                self.assertEqual(library.books("Sains")[0][2], 1)
                self.assertEqual(library.books("Lain")[0][2], 5)
            finally:
                root.destroy()


if __name__ == "__main__":
    unittest.main()
