"""Northside Community Cafe customer survey (EXAMPLE PROJECT: the cafe, people and data are invented).

Reproduces every table in findings.md from the file exactly as supplied.

    python analysis.py cafe-survey-original.csv

Needs only Python 3.8 or newer; no extra packages. The original file is never changed: the checked data is
written to cafe-survey-checked.csv next to it, and each change is counted so it can be compared with
changes.md.
"""
import csv
import math
import re
import sys
from collections import Counter, OrderedDict
from statistics import mean, median

SOURCE = sys.argv[1] if len(sys.argv) > 1 else "cafe-survey-original.csv"
CHECKED = "cafe-survey-checked.csv"
WAIT_BANDS = [("0-5 minutes", 0, 5), ("6-10 minutes", 6, 10), ("11-15 minutes", 11, 15), ("16 minutes or more", 16, 10 ** 6)]
# Comment themes are coded by keywords agreed in the scope. A comment can mention more than one theme.
THEMES = OrderedDict([
    ("Coffee and food", ("coffee", "flat white", "cake")),
    ("Friendly staff", ("staff", "team", "friendly")),
    ("Waiting and queues", ("wait", "queue", "slow")),
    ("Price", ("price", "pricey", "expensive")),
    ("Seating and space", ("seat", "cosy", "quiet", "comfortable")),
])


def load(path):
    with open(path, newline="", encoding="utf-8") as handle:
        return list(csv.DictReader(handle))


def check(rows):
    """Apply the agreed checks and return (checked rows, counts of every change)."""
    counts = Counter()
    seen, checked = set(), []
    for row in rows:
        key = tuple(row.values())
        if key in seen:  # the form tool sometimes submitted the same response twice
            counts["exact duplicate removed"] += 1
            continue
        seen.add(key)
        row = dict(row)
        trimmed = row["comment"].strip()
        if trimmed != row["comment"]:
            counts["comment spaces trimmed"] += 1
        row["comment"] = trimmed
        digits = re.findall(r"\d+", row["wait_time"])
        if not digits:
            counts["wait time blank (kept as missing)"] += 1
            row["wait_minutes"] = ""
        else:
            if row["wait_time"].strip() != digits[0]:
                counts["wait time text converted to minutes"] += 1
            row["wait_minutes"] = digits[0]
        rating = row["satisfaction"].strip()
        if rating == "":
            counts["rating blank (kept as missing)"] += 1
        elif rating not in {"1", "2", "3", "4", "5"}:
            counts["rating outside 1-5 set to missing"] += 1
            rating = ""
        row["satisfaction"] = rating
        checked.append(row)
    return checked, counts


def pct(part, whole):
    return 100 * part / whole if whole else float("nan")


def ranks(values):
    """Average ranks (1-based), with ties sharing the mean rank."""
    order = sorted(range(len(values)), key=lambda i: values[i])
    out = [0.0] * len(values)
    i = 0
    while i < len(order):
        j = i
        while j + 1 < len(order) and values[order[j + 1]] == values[order[i]]:
            j += 1
        for k in range(i, j + 1):
            out[order[k]] = (i + j) / 2 + 1
        i = j + 1
    return out


def spearman(x, y):
    rx, ry = ranks(x), ranks(y)
    mx, my = mean(rx), mean(ry)
    cov = sum((a - mx) * (b - my) for a, b in zip(rx, ry))
    return cov / math.sqrt(sum((a - mx) ** 2 for a in rx) * sum((b - my) ** 2 for b in ry))


def summarise(ratings):
    satisfied = sum(1 for r in ratings if r >= 4)
    return len(ratings), mean(ratings), median(ratings), pct(satisfied, len(ratings)), satisfied


def main():
    original = load(SOURCE)
    rows, counts = check(original)
    with open(CHECKED, "w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
        writer.writeheader()
        writer.writerows(rows)

    print(f"Responses supplied: {len(original)}; after checks: {len(rows)}")
    for change, n in sorted(counts.items()):
        print(f"  {change}: {n}")

    rated = [r for r in rows if r["satisfaction"]]
    print(f"\nResponses with a usable rating: {len(rated)}")

    print("\nTable 1. Satisfaction by day of visit (ratings 1-5; 'satisfied' means 4 or 5)")
    print(f"{'Day':<10}{'Rated':>7}{'Mean':>7}{'Median':>8}{'Satisfied':>11}{'Median wait':>13}")
    groups = {}
    for day in ("Weekday", "Weekend"):
        values = [int(r["satisfaction"]) for r in rated if r["visit_day"] == day]
        waits = [int(r["wait_minutes"]) for r in rows if r["visit_day"] == day and r["wait_minutes"]]
        groups[day] = summarise(values)
        n, avg, mid, share, _ = groups[day]
        print(f"{day:<10}{n:>7}{avg:>7.2f}{mid:>8.1f}{share:>10.1f}%{median(waits):>9.1f} min")
    (n1, _, _, p1, s1), (n2, _, _, p2, s2) = groups["Weekday"], groups["Weekend"]
    diff = (s1 / n1) - (s2 / n2)
    se = math.sqrt((s1 / n1) * (1 - s1 / n1) / n1 + (s2 / n2) * (1 - s2 / n2) / n2)
    print(f"Difference in % satisfied (weekday minus weekend): {100 * diff:.1f} points, "
          f"95% CI {100 * (diff - 1.96 * se):.1f} to {100 * (diff + 1.96 * se):.1f}")

    print("\nTable 2. Satisfaction by waiting time")
    print(f"{'Wait':<20}{'Rated':>7}{'Mean':>7}{'Satisfied':>11}")
    both = [r for r in rated if r["wait_minutes"]]
    for label, low, high in WAIT_BANDS:
        values = [int(r["satisfaction"]) for r in both if low <= int(r["wait_minutes"]) <= high]
        n, avg, _, share, _ = summarise(values)
        print(f"{label:<20}{n:>7}{avg:>7.2f}{share:>10.1f}%")
    print("Satisfied, by day, within the same waiting time:")
    for label, low, high in WAIT_BANDS:
        parts = []
        for day in ("Weekday", "Weekend"):
            values = [int(r["satisfaction"]) for r in both if r["visit_day"] == day and low <= int(r["wait_minutes"]) <= high]
            parts.append(f"{day.lower()} {pct(sum(1 for v in values if v >= 4), len(values)):.0f}% of {len(values)}")
        print(f"  {label:<20}{'; '.join(parts)}")
    rho = spearman([int(r["wait_minutes"]) for r in both], [int(r["satisfaction"]) for r in both])
    print(f"Spearman correlation between wait (minutes) and rating: {rho:.2f} (n = {len(both)})")

    print("\nTable 3. What comments mention (keyword coding; a comment can mention several themes)")
    commented = [r["comment"].lower() for r in rows if r["comment"]]
    print(f"Responses with a comment: {len(commented)}")
    print(f"{'Theme':<22}{'Comments':>9}{'Share':>8}")
    for theme, words in THEMES.items():
        n = sum(1 for text in commented if any(word in text for word in words))
        print(f"{theme:<22}{n:>9}{pct(n, len(commented)):>7.1f}%")


if __name__ == "__main__":
    main()
