#!/usr/bin/env python3
"""Расчёт выручки, расходов и прибыли по месяцам и регионам.

Источник: маршруты/данные/продажи.json (данные вымышлены).
Единица измерения берётся из поля "unit" исходного файла.
Результат: результаты/продажи-сводка.xlsx (данные, сводная таблица, график выручки).

Запуск:
    .venv/bin/python "сборка/продажи-расчёт.py"
"""

from __future__ import annotations

import json
from collections import OrderedDict
from pathlib import Path

from openpyxl import Workbook
from openpyxl.chart import BarChart, Reference
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter

BASE = Path(__file__).resolve().parent.parent
SOURCE = BASE / "маршруты" / "данные" / "продажи.json"
OUTPUT = BASE / "результаты" / "продажи-сводка.xlsx"

FIELDS = (("Выручка", "revenue"), ("Расходы", "expenses"), ("Прибыль", "profit"))

HEADER_FONT = Font(bold=True)
HEADER_FILL = PatternFill("solid", fgColor="DDEBF7")
TOTAL_FONT = Font(bold=True)


def style_header(ws, row: int, first_col: int, last_col: int) -> None:
    for col in range(first_col, last_col + 1):
        cell = ws.cell(row=row, column=col)
        cell.font = HEADER_FONT
        cell.fill = HEADER_FILL
        cell.alignment = Alignment(horizontal="center")


def style_totals(ws, row: int, first_col: int, last_col: int) -> None:
    for col in range(first_col, last_col + 1):
        ws.cell(row=row, column=col).font = TOTAL_FONT


def autofit(ws, padding: int = 2) -> None:
    for column in ws.columns:
        width = max(len(str(cell.value if cell.value is not None else "")) for cell in column)
        ws.column_dimensions[get_column_letter(column[0].column)].width = width + padding



def load_rows(path: Path) -> tuple[str, list[dict]]:
    payload = json.loads(path.read_text(encoding="utf-8"))
    rows = payload["rows"]
    for row in rows:
        for field in ("month", "region", "revenue", "expenses"):
            if field not in row:
                raise ValueError(f"в строке нет обязательного поля {field!r}: {row}")
    return payload["unit"], rows


def profit(row: dict) -> int:
    return row["revenue"] - row["expenses"]


def aggregate(rows: list[dict], key: str) -> "OrderedDict[str, dict]":
    """Суммирует выручку и расходы по значению поля key, считает прибыль."""
    totals: "OrderedDict[str, dict]" = OrderedDict()
    for row in rows:
        bucket = totals.setdefault(row[key], {"revenue": 0, "expenses": 0})
        bucket["revenue"] += row["revenue"]
        bucket["expenses"] += row["expenses"]
    for bucket in totals.values():
        bucket["profit"] = bucket["revenue"] - bucket["expenses"]
    return totals


def main() -> int:
    unit, rows = load_rows(SOURCE)

    by_month = aggregate(rows, "month")
    by_region = aggregate(rows, "region")
    months = list(by_month)
    regions = list(by_region)

    grand_revenue = sum(v["revenue"] for v in by_month.values())
    grand_expenses = sum(v["expenses"] for v in by_month.values())
    grand_profit = grand_revenue - grand_expenses

    # --- Контроль итогов -------------------------------------------------
    checks = {
        "выручка: сумма по месяцам = сумма по регионам": (
            sum(v["revenue"] for v in by_region.values()) == grand_revenue
        ),
        "расходы: сумма по месяцам = сумма по регионам": (
            sum(v["expenses"] for v in by_region.values()) == grand_expenses
        ),
        "прибыль = выручка - расходы": grand_profit == grand_revenue - grand_expenses,
        "прибыль: сумма по регионам = итог": (
            sum(v["profit"] for v in by_region.values()) == grand_profit
        ),
        "итог по месяцам сходится с исходными строками": (
            grand_revenue == sum(r["revenue"] for r in rows)
            and grand_expenses == sum(r["expenses"] for r in rows)
        ),
    }
    for name, ok in checks.items():
        print(f"[{'OK' if ok else 'FAIL'}] {name}")
    if not all(checks.values()):
        raise SystemExit("контроль итогов не пройден")

    # Значение показателя по конкретному месяцу и региону.
    def cell(month: str, region: str, field: str) -> int:
        row = next(r for r in rows if r["month"] == month and r["region"] == region)
        return profit(row) if field == "profit" else row[field]

    # Значение показателя по месяцу (все регионы) или по региону (все месяцы).
    def total_by_month(month: str, field: str) -> int:
        return by_month[month][field]

    def total_by_region(region: str, field: str) -> int:
        return by_region[region][field]

    wb = Workbook()

    # --- Лист 1: исходные данные с построчной прибылью -------------------
    ws_data = wb.active
    ws_data.title = "Исходные данные"
    ws_data.append([f"Единица измерения: {unit}"])
    ws_data.append(["Месяц", "Регион", "Выручка", "Расходы", "Прибыль"])
    for row in rows:
        ws_data.append([row["month"], row["region"], row["revenue"],
                        row["expenses"], profit(row)])

    # --- Лист 2: сводная таблица месяц x регион --------------------------
    ws_pivot = wb.create_sheet("Сводная")
    ws_pivot.append([f"Единица измерения: {unit}"])
    ws_pivot.append(["Регион", "Показатель", *months, "Итого"])
    for region in regions:
        for label, field in FIELDS:
            values = [cell(m, region, field) for m in months]
            ws_pivot.append([region, label, *values, total_by_region(region, field)])
        ws_pivot.append([])
    for label, field in FIELDS:
        values = [total_by_month(m, field) for m in months]
        grand = {"revenue": grand_revenue, "expenses": grand_expenses,
                 "profit": grand_profit}[field]
        ws_pivot.append(["Итого", label, *values, grand])
        style_totals(ws_pivot, ws_pivot.max_row, 1, 2 + len(months))
    style_header(ws_data, 2, 1, 5)
    style_header(ws_pivot, 2, 1, 2 + len(months))
    autofit(ws_data)
    autofit(ws_pivot)

    # --- Лист 3: данные для графика выручки ------------------------------
    ws_chart = wb.create_sheet("График выручки")
    ws_chart.append([f"Выручка по месяцам, {unit}"])
    ws_chart.append(["Месяц", *regions, "Итого"])
    for m in months:
        ws_chart.append([m, *[cell(m, region, "revenue") for region in regions],
                         total_by_month(m, "revenue")])

    chart = BarChart()
    chart.type = "col"
    chart.grouping = "clustered"
    chart.title = f"Выручка по месяцам, {unit}"
    chart.y_axis.title = "Выручка"
    chart.x_axis.title = "Месяц"
    data = Reference(ws_chart, min_col=2, max_col=1 + len(regions),
                     min_row=2, max_row=2 + len(months))
    categories = Reference(ws_chart, min_col=1, min_row=3, max_row=2 + len(months))
    chart.add_data(data, titles_from_data=True)
    chart.set_categories(categories)
    ws_chart.add_chart(chart, "A7")
    style_header(ws_chart, 2, 1, 2 + len(regions))
    autofit(ws_chart)

    OUTPUT.parent.mkdir(parents=True, exist_ok=True)
    wb.save(OUTPUT)

    print(f"\nЕдиница измерения: {unit}")
    print(f"Месяцы: {', '.join(months)}")
    print(f"Регионы: {', '.join(regions)}")
    for m in months:
        print(f"  {m}: выручка {total_by_month(m, 'revenue')}, "
              f"расходы {total_by_month(m, 'expenses')}, "
              f"прибыль {total_by_month(m, 'profit')}")
    print(f"ИТОГО: выручка {grand_revenue}, расходы {grand_expenses}, прибыль {grand_profit}")
    print(f"\nФайл: {OUTPUT}")
    return 0


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