"""Summarise an access log: status codes, slowest pages, 404s worth redirecting, hourly traffic.
Usage: python parse_log.py access.log  -> log_summary.xlsx, not_found.csv, slow_pages.csv"""
import re, sys, csv, statistics
from collections import Counter, defaultdict
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
LINE = re.compile(r'(?P<ip>\S+) \S+ \S+ \[(?P<ts>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) [^"]+" (?P<status>\d{3}) (?P<size>\d+) "[^"]*" "(?P<ua>[^"]*)" (?P<ms>\d+)ms')
def main(fn):
    ok, bad = [], 0
    for line in open(fn):
        m = LINE.match(line.strip())
        if m: ok.append(m.groupdict())
        elif line.strip(): bad += 1
    status = Counter(r["status"] for r in ok); nf = Counter(r["path"] for r in ok if r["status"] == "404")
    times = defaultdict(list); hours = Counter(r["ts"][12:14] for r in ok)
    for r in ok: times[r["path"]].append(int(r["ms"]))
    slow = sorted(((p, len(v), round(statistics.median(v)), max(v)) for p, v in times.items()), key=lambda x: -x[2])
    with open("not_found.csv", "w", newline="") as f: w = csv.writer(f); w.writerow(["path", "hits"]); w.writerows(nf.most_common())
    with open("slow_pages.csv", "w", newline="") as f: w = csv.writer(f); w.writerow(["path", "requests", "median_ms", "max_ms"]); w.writerows(slow)
    wb = Workbook(); ws = wb.active; ws.title = "Summary"; b = Font(bold=True); fill = PatternFill("solid", fgColor="DDEBF7")
    for row in [["Access log summary"], ["Lines parsed", len(ok)], ["Lines skipped (bad format)", bad], ["Server errors (5xx)", sum(v for k, v in status.items() if k.startswith("5"))],
                ["Not found (404)", status["404"]], ["Distinct visitors (IP)", len({r['ip'] for r in ok})], [], ["Status", "Requests"]] + [[k, v] for k, v in sorted(status.items())]:
        ws.append(row)
    ws["A1"].font = Font(bold=True, size=14); ws.column_dimensions["A"].width = 30
    for name, hdr, data in [("Slow pages", ["Path", "Requests", "Median ms", "Max ms"], slow), ("404s", ["Path", "Hits"], nf.most_common()),
                            ("By hour", ["Hour", "Requests"], sorted(hours.items()))]:
        s = wb.create_sheet(name); s.append(hdr); [s.append(list(r)) for r in data]; s.column_dimensions["A"].width = 28
        for c in s[1]: c.font = b; c.fill = fill
    wb.save("log_summary.xlsx")
    print(f"parsed {len(ok)} lines, skipped {bad}; 404s {status['404']}; 5xx {status['500']}; slowest {slow[0][0]} median {slow[0][2]}ms")
if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else "access.log")
