"""Turn a sales CSV into two report-ready charts.

Fixes over the naive version:

  1. The original never ran at all: `"Teknoloji_Satis":,` is a syntax error, so
     the file failed at import before drawing anything.
  2. `rcParams["font.sans-serif"] = "Arial"` assigns a string where matplotlib
     expects a list of fallbacks, and Arial does not exist on most Linux boxes.
     A fallback chain keeps Turkish characters rendering on any machine.
  3. The pie chart used a drop shadow and an exploded slice. Both add ink without
     adding information, and angle is already the hardest encoding to compare.
     Ranked horizontal bars answer "which region is biggest, and by how much"
     directly, so that is what this draws.
  4. Data is read from a CSV instead of being pasted into the source.

    python3 sales_charts.py --demo                  # write a sample CSV and chart it
    python3 sales_charts.py sales.csv --outdir out

CSV columns: month, category, region, amount

Requires: pandas, matplotlib
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

import matplotlib
matplotlib.use("Agg")  # no display needed; safe under cron
import matplotlib.pyplot as plt
import pandas as pd

# Categorical slots 1 and 2 of a colourblind-validated order.
# Worst-pair separation: CVD deltaE 24.7, normal vision 33.6 — both well clear.
SERIES_COLOURS = ["#2a78d6", "#eb6834"]
SINGLE_HUE = "#2a78d6"

INK = "#1a1a19"
INK_SOFT = "#52514e"
GRID = "#e4e3df"
SURFACE = "#fcfcfb"

REQUIRED_COLUMNS = {"month", "category", "region", "amount"}


def configure_style() -> None:
    # A chain, not a single name: the first font that exists on the machine wins,
    # so Turkish characters survive on Linux as well as macOS.
    plt.rcParams["font.family"] = "sans-serif"
    plt.rcParams["font.sans-serif"] = [
        "Inter", "Helvetica Neue", "Helvetica", "Arial",
        "DejaVu Sans", "Liberation Sans", "sans-serif",
    ]
    plt.rcParams["axes.facecolor"] = SURFACE
    plt.rcParams["figure.facecolor"] = SURFACE
    plt.rcParams["axes.edgecolor"] = GRID
    plt.rcParams["text.color"] = INK
    plt.rcParams["axes.labelcolor"] = INK_SOFT
    plt.rcParams["xtick.color"] = INK_SOFT
    plt.rcParams["ytick.color"] = INK_SOFT
    plt.rcParams["axes.titlesize"] = 13
    plt.rcParams["axes.titleweight"] = "bold"


def demo_frame() -> pd.DataFrame:
    months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"]
    tech = [120, 135, 128, 152, 168, 161, 185, 199]
    fashion = [96, 104, 118, 112, 126, 141, 138, 147]
    regions = ["Marmara", "Central Anatolia", "Aegean", "Marmara",
               "Mediterranean", "Aegean", "Marmara", "Mediterranean"]
    rows = []
    for i, month in enumerate(months):
        rows.append({"month": month, "category": "Technology",
                     "region": regions[i], "amount": tech[i]})
        rows.append({"month": month, "category": "Fashion",
                     "region": regions[i], "amount": fashion[i]})
    return pd.DataFrame(rows)


def trend_chart(frame: pd.DataFrame, out: Path) -> None:
    """Change over time -> lines. One mark per series, labelled at its end."""
    pivot = frame.pivot_table(index="month", columns="category",
                              values="amount", aggfunc="sum", sort=False)

    fig, ax = plt.subplots(figsize=(9, 4.6))

    for index, category in enumerate(pivot.columns):
        colour = SERIES_COLOURS[index % len(SERIES_COLOURS)]
        ax.plot(pivot.index, pivot[category], color=colour,
                linewidth=2, marker="o", markersize=5,
                markeredgecolor=SURFACE, markeredgewidth=1.5, label=category)
        # Direct label at the final point, rather than a number on every point.
        ax.annotate(f"{category}  {pivot[category].iloc[-1]:,.0f}",
                    xy=(len(pivot.index) - 1, pivot[category].iloc[-1]),
                    xytext=(8, 0), textcoords="offset points",
                    va="center", fontsize=9, color=INK_SOFT)

    ax.set_title("Monthly sales by category", loc="left", pad=14)
    ax.set_ylabel("Sales")
    ax.grid(axis="y", color=GRID, linewidth=1)
    ax.set_axisbelow(True)
    for side in ("top", "right", "left"):
        ax.spines[side].set_visible(False)
    ax.margins(x=0.02)
    ax.set_xlim(-0.3, len(pivot.index) - 0.3)
    # Identity is never colour alone: legend plus the direct labels above.
    ax.legend(frameon=False, loc="upper left", fontsize=9)
    fig.subplots_adjust(right=0.82)

    fig.savefig(out, dpi=200, bbox_inches="tight")
    plt.close(fig)
    print(f"Wrote {out}")


def region_chart(frame: pd.DataFrame, out: Path) -> None:
    """Magnitude across categories -> ranked horizontal bars, not a pie."""
    totals = frame.groupby("region")["amount"].sum().sort_values()
    share = totals / totals.sum() * 100

    fig, ax = plt.subplots(figsize=(9, 0.62 * len(totals) + 2.0))
    ax.barh(totals.index, totals.values, color=SINGLE_HUE, height=0.62)

    span = totals.max()
    for y, (value, pct) in enumerate(zip(totals.values, share.values)):
        ax.annotate(f"{value:,.0f}   {pct:.1f}%",
                    xy=(value, y), xytext=(8, 0), textcoords="offset points",
                    va="center", fontsize=9, color=INK_SOFT)

    ax.set_title("Total sales by region", loc="left", pad=14)
    ax.set_xlim(0, span * 1.26)
    ax.xaxis.set_visible(False)
    for side in ("top", "right", "bottom"):
        ax.spines[side].set_visible(False)
    ax.spines["left"].set_color(GRID)
    ax.tick_params(axis="y", length=0)

    fig.savefig(out, dpi=200, bbox_inches="tight")
    plt.close(fig)
    print(f"Wrote {out}")


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Chart a sales CSV.")
    parser.add_argument("csv", nargs="?", type=Path, help="CSV with month, category, region, amount")
    parser.add_argument("--demo", action="store_true", help="Use built-in sample data")
    parser.add_argument("--outdir", type=Path, default=Path("."), help="Where to write the PNGs")
    args = parser.parse_args(argv)

    if args.demo:
        frame = demo_frame()
    elif args.csv:
        try:
            frame = pd.read_csv(args.csv)
        except (OSError, pd.errors.ParserError) as exc:
            print(f"Could not read {args.csv}: {exc}", file=sys.stderr)
            return 1
        missing = REQUIRED_COLUMNS - set(frame.columns)
        if missing:
            print(f"{args.csv} is missing column(s): {', '.join(sorted(missing))}", file=sys.stderr)
            return 1
    else:
        parser.error("Give a CSV path, or --demo for sample data.")

    frame["amount"] = pd.to_numeric(frame["amount"], errors="coerce")
    dropped = int(frame["amount"].isna().sum())
    if dropped:
        print(f"WARNING: dropped {dropped} row(s) with a non-numeric amount", file=sys.stderr)
        frame = frame.dropna(subset=["amount"])

    if frame.empty:
        print("Nothing left to chart after cleaning.", file=sys.stderr)
        return 1

    args.outdir.mkdir(parents=True, exist_ok=True)
    configure_style()
    trend_chart(frame, args.outdir / "sales_trend.png")
    region_chart(frame, args.outdir / "sales_by_region.png")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
