"""Automatic checks for Tutorial 3.

Run them with:

    python -m unittest

A test is a small piece of code that uses your code and then says
"this should be true". Flask gives us a "test client", which is a pretend
browser: it can visit a page or submit a form without you clicking
anything and without starting a real server.

These tests edit data/inventory.json, so each test saves a copy of the
file first and puts it back afterwards.
"""

import os
import unittest

import generate_data
from app import app
from inventory import (
    DATA_FILE,
    adjust_quantity,
    count_low_stock,
    find_item,
    load_items,
    search_items,
)


class InventoryFunctionTests(unittest.TestCase):
    """Check the plain Python functions in inventory.py, no web involved."""

    def setUp(self):
        # A tiny made-up list so these tests do not depend on the real file.
        self.items = [
            {"sku": "OFF-1", "name": "Stapler", "category": "Office",
             "quantity": 4, "location": "Aisle 1"},
            {"sku": "KIT-2", "name": "Ground Coffee", "category": "Kitchen",
             "quantity": 30, "location": "Aisle 2"},
        ]

    def test_search_matches_name_ignoring_case(self):
        matches = search_items(self.items, "COFFEE")
        self.assertEqual(len(matches), 1)
        self.assertEqual(matches[0]["sku"], "KIT-2")

    def test_search_matches_category(self):
        matches = search_items(self.items, "office")
        self.assertEqual(len(matches), 1)
        self.assertEqual(matches[0]["name"], "Stapler")

    def test_empty_search_returns_everything(self):
        matches = search_items(self.items, "   ")
        self.assertEqual(len(matches), 2)

    def test_search_with_no_match_returns_empty_list(self):
        matches = search_items(self.items, "bicycle")
        self.assertEqual(matches, [])

    def test_find_item_is_not_case_sensitive(self):
        item = find_item(self.items, "kit-2")
        self.assertIsNotNone(item)
        self.assertEqual(item["name"], "Ground Coffee")

    def test_find_item_returns_none_when_missing(self):
        self.assertIsNone(find_item(self.items, "NOPE-9"))

    def test_adjust_adds_to_quantity(self):
        item, message = adjust_quantity(self.items, "OFF-1", 6)
        self.assertEqual(item["quantity"], 10)
        self.assertIn("4 to 10", message)

    def test_adjust_never_goes_below_zero(self):
        item, message = adjust_quantity(self.items, "OFF-1", -100)
        self.assertEqual(item["quantity"], 0)

    def test_adjust_unknown_sku_returns_none(self):
        item, message = adjust_quantity(self.items, "NOPE-9", 1)
        self.assertIsNone(item)
        self.assertIn("No item found", message)

    def test_count_low_stock(self):
        self.assertEqual(count_low_stock(self.items, 10), 1)


class WebPageTests(unittest.TestCase):
    """Visit the pages with Flask's pretend browser."""

    def setUp(self):
        # Make sure the data file exists before any test runs.
        if not os.path.exists(DATA_FILE):
            generate_data.main()

        # Keep a copy of the file so a test that changes stock can undo it.
        with open(DATA_FILE, "r", encoding="utf-8") as data_file:
            self.original_data = data_file.read()

        app.config["TESTING"] = True
        self.client = app.test_client()

    def tearDown(self):
        # Put the original data back, whatever the test did.
        with open(DATA_FILE, "w", encoding="utf-8") as data_file:
            data_file.write(self.original_data)

    def test_home_page_loads_and_lists_items(self):
        response = self.client.get("/")
        # 200 is the code a web server sends for "here is your page".
        self.assertEqual(response.status_code, 200)
        page = response.get_data(as_text=True)
        self.assertIn("Inventory Lookup", page)
        self.assertIn("OFF-1001", page)
        self.assertIn("Ballpoint Pens", page)

    def test_search_page_shows_only_matches(self):
        response = self.client.get("/search?q=coffee")
        self.assertEqual(response.status_code, 200)
        page = response.get_data(as_text=True)
        self.assertIn("Ground Coffee", page)
        self.assertIn("Coffee Filters", page)
        self.assertNotIn("Ballpoint Pens", page)

    def test_search_by_sku(self):
        response = self.client.get("/search?q=OFF-1001")
        page = response.get_data(as_text=True)
        self.assertIn("Ballpoint Pens", page)
        self.assertNotIn("Ground Coffee", page)

    def test_search_with_no_results_says_so(self):
        response = self.client.get("/search?q=zzzzz")
        self.assertEqual(response.status_code, 200)
        self.assertIn("Nothing matched", response.get_data(as_text=True))

    def test_adjust_form_changes_the_saved_quantity(self):
        before = find_item(load_items(), "OFF-1001")["quantity"]

        response = self.client.post(
            "/adjust",
            data={"sku": "OFF-1001", "change": "5"},
            follow_redirects=True,
        )
        self.assertEqual(response.status_code, 200)

        after = find_item(load_items(), "OFF-1001")["quantity"]
        self.assertEqual(after, before + 5)
        self.assertIn("OFF-1001 changed from", response.get_data(as_text=True))

    def test_adjust_with_a_word_instead_of_a_number_is_handled(self):
        response = self.client.post(
            "/adjust",
            data={"sku": "OFF-1001", "change": "five"},
            follow_redirects=True,
        )
        self.assertEqual(response.status_code, 200)
        self.assertIn("is not a whole number", response.get_data(as_text=True))

    def test_adjust_with_unknown_sku_is_handled(self):
        response = self.client.post(
            "/adjust",
            data={"sku": "ZZZ-9999", "change": "1"},
            follow_redirects=True,
        )
        self.assertEqual(response.status_code, 200)
        self.assertIn("No item found", response.get_data(as_text=True))

    def test_get_request_to_adjust_is_not_allowed(self):
        response = self.client.get("/adjust")
        # 405 means "that address exists, but not with this kind of request".
        self.assertEqual(response.status_code, 405)


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