"""The inventory "logic" for Tutorial 3.

This file knows how to read the item list, search it, and change a
quantity. It knows nothing about web pages. Keeping the thinking here and
the web pages in app.py is called "separating logic from presentation",
and it is one of the main ideas of this tutorial.

Because there is no web code in this file, you can also try these
functions on their own in a Python prompt.
"""

import json
import os

# Build the path to data/inventory.json based on where THIS file lives.
THIS_FOLDER = os.path.dirname(os.path.abspath(__file__))
DATA_FILE = os.path.join(THIS_FOLDER, "data", "inventory.json")


def load_items(path=DATA_FILE):
    """Read the JSON file and return a list of item dictionaries.

    If the file is missing we return an empty list instead of crashing,
    so the web page can show a friendly "run generate_data.py" message.
    """
    if not os.path.exists(path):
        return []

    with open(path, "r", encoding="utf-8") as json_file:
        items = json.load(json_file)

    return items


def save_items(items, path=DATA_FILE):
    """Write the list of items back to the JSON file."""
    with open(path, "w", encoding="utf-8") as json_file:
        json.dump(items, json_file, indent=2)
        json_file.write("\n")


def search_items(items, query):
    """Return only the items whose SKU, name, or category matches the query.

    The match is not case sensitive: "pens", "Pens", and "PENS" all work.
    An empty query returns every item.
    """
    # strip() removes spaces at the start and end of what the person typed.
    query = query.strip().lower()

    if query == "":
        return items

    matches = []
    for item in items:
        # Glue the three searchable fields into one lowercase string.
        haystack = item["sku"] + " " + item["name"] + " " + item["category"]
        haystack = haystack.lower()

        if query in haystack:
            matches.append(item)

    return matches


def find_item(items, sku):
    """Return the one item with this SKU, or None if there is no match."""
    sku = sku.strip().upper()

    for item in items:
        if item["sku"] == sku:
            return item

    return None


def adjust_quantity(items, sku, change):
    """Add `change` to an item's quantity and return (item, message).

    `change` can be positive (a delivery arrived) or negative (something
    was used). Quantity is never allowed to drop below zero.

    We return two things: the item we changed (or None if we could not
    find it) and a short sentence to show the person.
    """
    item = find_item(items, sku)

    if item is None:
        return None, "No item found with SKU " + sku.strip().upper() + "."

    new_quantity = item["quantity"] + change

    if new_quantity < 0:
        new_quantity = 0

    old_quantity = item["quantity"]
    item["quantity"] = new_quantity

    message = (
        item["sku"]
        + " changed from "
        + str(old_quantity)
        + " to "
        + str(new_quantity)
        + "."
    )
    return item, message


def count_low_stock(items, threshold=10):
    """Count how many items have a quantity at or below the threshold."""
    low = 0

    for item in items:
        if item["quantity"] <= threshold:
            low = low + 1

    return low
