#!/usr/bin/env python3
"""Rainfall-volume and fixed-area depth arithmetic with synthetic inputs only.

Usage: python3 rainfall-depth-teaching.py [output-directory]
Python 3.9+; standard library only. This is not a flood or drainage model.
"""

from datetime import datetime
from decimal import Decimal
import json
from pathlib import Path
import platform
import sys


def balance(rain_mm, contributing_m2, retained_fraction, receiving_m2):
    rain, area, fraction, receiving = [Decimal(str(x)) for x in
                                     (rain_mm, contributing_m2, retained_fraction, receiving_m2)]
    if not all(x.is_finite() for x in (rain, area, fraction, receiving)):
        raise ValueError("All inputs must be finite.")
    if rain < 0 or area <= 0 or receiving <= 0:
        raise ValueError("Rain must be nonnegative; both areas must be positive.")
    if not 0 <= fraction <= 1:
        raise ValueError("Retained fraction must be between zero and one.")
    if receiving > area:
        raise ValueError("This model includes the receiving area in the contributing area.")
    input_litres = rain * area
    retained_litres = input_litres * fraction
    depth_mm = retained_litres / receiving
    return {"rain_mm": str(rain), "contributing_m2": str(area),
            "retained_fraction": str(fraction), "receiving_m2": str(receiving),
            "input_litres": str(input_litres), "retained_litres": str(retained_litres),
            "model_mean_depth_mm": str(depth_mm)}


def main():
    output = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("rainfall-depth-output")
    output.mkdir(parents=True, exist_ok=True)
    scenarios = []
    for label, inputs in (
            ("same_area_all_retained", ("10", "100", "1", "100")),
            ("same_area_30_percent_retained", ("10", "100", "0.3", "100")),
            ("all_water_concentrated_into_20_m2", ("10", "100", "1", "20"))):
        scenarios.append({"case": label, **balance(*inputs)})
    assert [x["model_mean_depth_mm"] for x in scenarios] == ["10", "3.0", "50"]
    unit = balance("1", "1", "1", "1")
    zero_rain = balance("0", "100", "1", "100")
    zero_retention = balance("10", "100", "0", "100")
    assert unit["input_litres"] == "1"
    assert zero_rain["model_mean_depth_mm"] == zero_retention["model_mean_depth_mm"] == "0"
    rejected = []
    for label, inputs in (
            ("negative_rain", ("-1", "100", "1", "100")),
            ("zero_contributing_area", ("10", "0", "1", "100")),
            ("zero_receiving_area", ("10", "100", "1", "0")),
            ("negative_fraction", ("10", "100", "-0.1", "100")),
            ("fraction_above_one", ("10", "100", "1.2", "100")),
            ("receiving_area_not_included", ("10", "100", "1", "120")),
            ("nonfinite_rain", ("NaN", "100", "1", "100"))):
        try:
            balance(*inputs)
        except ValueError as error:
            rejected.append({"case": label, "inputs": list(inputs), "error": str(error)})
        else:
            raise AssertionError("Expected rejection: " + label)
    profiles = []
    for label, values in (
            ("uniform_24_hours", ["1.25"] * 24),
            ("all_in_one_clock_hour", ["30"] + ["0"] * 23),
            ("first_six_clock_hours", ["5"] * 6 + ["0"] * 18)):
        depths = [Decimal(x) for x in values]
        total = sum(depths, Decimal(0))
        profiles.append({"case": label, "clock_hour_amounts_mm": values,
                         "total_24h_mm": str(total), "mean_24h_mm_per_h": str(total / 24),
                         "largest_clock_hour_amount_mm": str(max(depths))})
    assert all(Decimal(x["total_24h_mm"]) == 30 for x in profiles)
    assert all(Decimal(x["mean_24h_mm_per_h"]) == Decimal("1.25") for x in profiles)
    report = {
        "recorded_at": datetime.now().astimezone().isoformat(),
        "environment": {"python": platform.python_version(), "system": platform.system(),
                        "macos": platform.mac_ver()[0], "machine": platform.machine()},
        "input_kind": "synthetic teaching assumptions, not observed rainfall",
        "assumptions": ["Uniform liquid rainfall over a horizontal contributing area.",
                        "The contributing area already includes the receiving area.",
                        "No initial stored water or water contribution outside this area.",
                        "Retained fraction is a supplied final-volume assumption, not an estimated runoff coefficient.",
                        "Constant receiving area; depth is a model mean, not a point water level."],
        "scenarios": scenarios,
        "checks": {"one_mm_one_m2": unit, "zero_rain": zero_rain,
                   "zero_retention": zero_retention, "rejected_inputs": rejected},
        "hourly_teaching_profiles": profiles,
        "scope": "Arithmetic and input checks only. No physical rain gauge, real precipitation series, terrain, infiltration, drainage routing, overflow, flood forecast or site water-level validation.",
    }
    (output / "results.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n",
                                         encoding="utf-8")
    print(json.dumps({"environment": report["environment"], "scenarios": scenarios,
                      "hourly_summaries": [{k: v for k, v in x.items() if k != "clock_hour_amounts_mm"}
                                           for x in profiles],
                      "rejected_inputs": len(rejected)}, indent=2))


if __name__ == "__main__":
    main()
