"""Tutorial 4 - turn a folder of JSON files into one plain-text report.

Run generate_data.py first, then run this file:

    python3 report_builder.py
    python3 report_builder.py --send

What it does:
  1. reads every .json file in data/daily/
  2. skips or flags anything broken instead of crashing
  3. writes reports/summary.txt
  4. with --send, also writes a pretend "email" to outbox/report_ready.txt
"""

import json
import sys
from pathlib import Path

HERE = Path(__file__).parent
DAILY_FOLDER = HERE / "data" / "daily"
REPORT_PATH = HERE / "reports" / "summary.txt"
OUTBOX_PATH = HERE / "outbox" / "report_ready.txt"


def money(number):
    """Turn 1234.5 into the text $1,234.50."""
    return "${:,.2f}".format(number)


def clean_amount(raw_value):
    """Return (amount, problem_text).

    problem_text is None when the value was usable. We never guess a
    price: anything we cannot read counts as 0.00 and gets reported.
    """
    if raw_value is None:
        return 0.0, 'missing "amount"'
    if isinstance(raw_value, bool):
        return 0.0, '"amount" is true/false, not a price'
    if isinstance(raw_value, (int, float)):
        return float(raw_value), None

    # Strings happen when a person typed the value in by hand.
    text = str(raw_value).strip().lstrip("$")
    try:
        return float(text), None
    except ValueError:
        return 0.0, 'could not read amount "{}"'.format(raw_value)


def clean_quantity(raw_value):
    """Return (quantity, problem_text) using the same rules as amounts."""
    if raw_value is None:
        return 0, 'missing "quantity"'
    if isinstance(raw_value, bool):
        return 0, '"quantity" is true/false, not a number'
    try:
        return int(raw_value), None
    except (TypeError, ValueError):
        return 0, 'could not read quantity "{}"'.format(raw_value)


def read_day_file(path):
    """Read one JSON file.

    Returns (day_data, problem_text). If the file cannot be read at all,
    day_data is None and problem_text says why. This is the try/except
    that keeps one bad file from stopping the whole report.
    """
    try:
        with open(path, "r", encoding="utf-8") as json_file:
            day_data = json.load(json_file)
    except json.JSONDecodeError as error:
        return None, "not valid JSON (line {}): {}".format(error.lineno, error.msg)
    except OSError as error:
        return None, "could not open the file: {}".format(error.strerror)

    if not isinstance(day_data, dict):
        return None, "expected a JSON object, found a {}".format(type(day_data).__name__)

    return day_data, None


def summarize_day(day_data, file_name):
    """Add up one day's orders and collect anything that looked wrong."""
    problems = []

    date_text = day_data.get("date")
    if not date_text:
        date_text = file_name.replace(".json", "")
        problems.append('missing "date", used the file name instead')

    orders = day_data.get("orders")
    if orders is None:
        orders = []
        problems.append('missing "orders", counted this day as 0 orders')
    elif not isinstance(orders, list):
        orders = []
        problems.append('"orders" was not a list, counted this day as 0 orders')

    order_count = 0
    item_count = 0
    revenue = 0.0

    for position, order in enumerate(orders, start=1):
        if not isinstance(order, dict):
            problems.append("order #{} is not an object, skipped".format(position))
            continue

        label = order.get("order_id", "order #{}".format(position))
        order_count = order_count + 1

        amount, amount_problem = clean_amount(order.get("amount"))
        if amount_problem:
            problems.append("{}: {}, counted as $0.00".format(label, amount_problem))
        revenue = revenue + amount

        quantity, quantity_problem = clean_quantity(order.get("quantity"))
        if quantity_problem:
            problems.append("{}: {}, counted as 0 items".format(label, quantity_problem))
        item_count = item_count + quantity

    return {
        "file_name": file_name,
        "date": date_text,
        "store": day_data.get("store", "Unknown store"),
        "order_count": order_count,
        "item_count": item_count,
        "revenue": round(revenue, 2),
        "problems": problems,
    }


def load_folder(folder_path):
    """Read every .json file in a folder.

    Returns (day_summaries, skipped_files). skipped_files holds
    (file_name, reason) pairs for files we could not read.
    """
    day_summaries = []
    skipped_files = []

    for path in sorted(Path(folder_path).glob("*.json")):
        day_data, problem = read_day_file(path)
        if day_data is None:
            skipped_files.append((path.name, problem))
        else:
            day_summaries.append(summarize_day(day_data, path.name))

    return day_summaries, skipped_files


def build_summary_text(day_summaries, skipped_files):
    """Build the whole text of reports/summary.txt as one string."""
    total_orders = sum(day["order_count"] for day in day_summaries)
    total_items = sum(day["item_count"] for day in day_summaries)
    total_revenue = sum(day["revenue"] for day in day_summaries)
    files_read = len(day_summaries) + len(skipped_files)

    if day_summaries:
        store = day_summaries[0]["store"]
    else:
        store = "Unknown store"

    lines = []
    lines.append("DAILY SALES SUMMARY")
    lines.append("=" * 52)
    lines.append("Store:       {}".format(store))
    lines.append("Files found: {}  ({} read, {} skipped)".format(
        files_read, len(day_summaries), len(skipped_files)))
    lines.append("")
    lines.append("TOTALS")
    lines.append("  Orders:  {}".format(total_orders))
    lines.append("  Items:   {}".format(total_items))
    lines.append("  Revenue: {}".format(money(total_revenue)))
    lines.append("")
    lines.append("BY DAY")

    if day_summaries:
        for day in day_summaries:
            lines.append("  {}   {:>3} orders   {:>3} items   {:>10}".format(
                day["date"], day["order_count"], day["item_count"], money(day["revenue"])))
    else:
        lines.append("  (no readable files)")

    problem_lines = []
    for day in day_summaries:
        for problem in day["problems"]:
            problem_lines.append("  {}: {}".format(day["file_name"], problem))
    for file_name, reason in skipped_files:
        problem_lines.append("  {}: SKIPPED - {}".format(file_name, reason))

    lines.append("")
    lines.append("PROBLEMS FOUND: {}".format(len(problem_lines)))
    if problem_lines:
        lines.extend(problem_lines)
    else:
        lines.append("  (none - every file and every field was readable)")

    lines.append("")
    return "\n".join(lines)


def build_outbox_text(summary_text, report_path):
    """Build the pretend email. Nothing is actually sent anywhere."""
    lines = []
    lines.append("TO:      manager@riverside-cafe.example")
    lines.append("SUBJECT: Daily sales summary is ready")
    lines.append("")
    lines.append("This is a mock message. No email was sent.")
    lines.append("The real report was saved to: {}".format(report_path))
    lines.append("")
    lines.append("-" * 52)
    lines.append(summary_text)
    return "\n".join(lines)


def write_text_file(path, text):
    """Write text to a file, creating the folder if it does not exist."""
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", encoding="utf-8") as text_file:
        text_file.write(text)
    return path


def main():
    send_it = "--send" in sys.argv

    print("Reading JSON files from: {}".format(DAILY_FOLDER))

    if not DAILY_FOLDER.is_dir():
        print("")
        print("That folder does not exist yet.")
        print("Run this first:  python3 generate_data.py")
        return 1

    day_summaries, skipped_files = load_folder(DAILY_FOLDER)

    if not day_summaries and not skipped_files:
        print("")
        print("No .json files in that folder.")
        print("Run this first:  python3 generate_data.py")
        return 1

    for day in day_summaries:
        print("  read {}  ->  {} orders, {}{}".format(
            day["file_name"],
            day["order_count"],
            money(day["revenue"]),
            "   ({} problem(s))".format(len(day["problems"])) if day["problems"] else "",
        ))
    for file_name, reason in skipped_files:
        print("  SKIPPED {}  ->  {}".format(file_name, reason))

    summary_text = build_summary_text(day_summaries, skipped_files)
    write_text_file(REPORT_PATH, summary_text)

    print("")
    print("Wrote report: {}".format(REPORT_PATH))

    if send_it:
        write_text_file(OUTBOX_PATH, build_outbox_text(summary_text, REPORT_PATH))
        print("Wrote mock send: {}  (pretend email - nothing left your computer)".format(OUTBOX_PATH))
    else:
        print("Tip: run it again with --send to also write outbox/report_ready.txt")

    return 0


if __name__ == "__main__":
    sys.exit(main())
