"""Q1-Q7 over the JSON-LD FULL variant: ijson streaming parser (spec 08).

Each coupon file is streamed event-by-event; scalar coupon fields appear
before the bulk arrays in the serialization, so filtering queries (Q2) can
abandon a non-matching file before parsing its megabytes of points.
"""

from __future__ import annotations

from pathlib import Path

import ijson

from common.queries import Q1_TRACK_CODE
from common.track_summary import summarize_track


class CouponDoc:
	__slots__ = ("coupon_code", "batch_code", "run_code", "au", "hardness", "tracks")

	def __init__(self) -> None:
		self.coupon_code = ""
		self.batch_code = ""
		self.run_code = ""
		self.au = 0.0
		self.hardness: list[float] = []
		self.tracks: list[dict] = []


def _scan(path: Path, au_range: tuple[float, float] | None = None) -> CouponDoc | None:
	"""Stream one coupon file; with au_range set, bail out early on mismatch."""
	doc = CouponDoc()
	track: dict | None = None
	with open(path, "rb") as fh:
		for prefix, event, value in ijson.parse(fh, use_float=True):
			if prefix == "coupon_code":
				doc.coupon_code = value
			elif prefix == "batch_code":
				doc.batch_code = value
			elif prefix == "run_code":
				doc.run_code = value
				if doc.run_code == "RESERVE":
					return None
			elif prefix == "au_wtpct_mean":
				doc.au = value
				if au_range and not au_range[0] <= value <= au_range[1]:
					return None
			elif prefix == "nanoindentation.indents.item.hardness_gpa":
				doc.hardness.append(value)
			elif prefix == "tracks.item" and event == "start_map":
				track = {"cycles": [], "cofs": []}
			elif track is not None:
				if prefix == "tracks.item.track_code":
					track["track_code"] = value
				elif prefix == "tracks.item.environment":
					track["environment"] = value
				elif prefix == "tracks.item.load_mn":
					track["load_mn"] = value
				elif prefix == "tracks.item.speed_mm_s":
					track["speed_mm_s"] = value
				elif prefix == "tracks.item.cycles.item.cycle":
					track["cycles"].append(value)
				elif prefix == "tracks.item.cycles.item.cof":
					track["cofs"].append(value)
				elif prefix == "tracks.item.wear.wear_volume_um3":
					track["wear"] = value
				elif prefix == "tracks.item" and event == "end_map":
					doc.tracks.append(track)
					track = None
	return doc


def _files(json_root: Path) -> list[Path]:
	return sorted((json_root / "full").glob("*.jsonld"))


def _mean(values: list[float]) -> float:
	total = 0.0
	for v in values:
		total += v
	return total / len(values)


def _cof_ss(doc: CouponDoc) -> float:
	return _mean([summarize_track(t["cofs"])[0] for t in doc.tracks])


def q1(json_root: Path) -> list[tuple]:
	coupon_code = Q1_TRACK_CODE.rsplit("-", 1)[0]
	doc = _scan(json_root / "full" / f"{coupon_code}.jsonld")
	for track in doc.tracks:
		if track["track_code"] == Q1_TRACK_CODE:
			return [(int(c), cof) for c, cof in zip(track["cycles"], track["cofs"])]
	raise LookupError(f"track {Q1_TRACK_CODE} not found")


def q2(json_root: Path) -> list[tuple]:
	out = []
	for path in _files(json_root):
		doc = _scan(path, au_range=(9.5, 10.5))
		if doc is None:
			continue
		out.append((doc.coupon_code, doc.tracks[0]["environment"], _cof_ss(doc)))
	return out


def q3(json_root: Path) -> list[tuple]:
	out = []
	for path in _files(json_root):
		doc = _scan(path)
		if doc is None:
			continue
		out.append((doc.coupon_code, doc.batch_code, _mean(doc.hardness), _cof_ss(doc)))
	return out


def q4(json_root: Path) -> list[tuple]:
	out = []
	for path in _files(json_root):
		doc = _scan(path)
		if doc is None:
			continue
		for track in doc.tracks:
			if track["environment"] != "dry_n2" or track["load_mn"] != 100.0:
				continue
			cof_ss = summarize_track(track["cofs"])[0]
			if cof_ss > 0.20:
				out.append((track["track_code"], cof_ss))
	return out


def q5(json_root: Path) -> list[tuple]:
	sums: dict[str, list[float]] = {}
	for path in _files(json_root):
		doc = _scan(path)
		if doc is None:
			continue
		acc = sums.setdefault(doc.batch_code, [0.0, 0.0])
		for track in doc.tracks:
			acc[0] += summarize_track(track["cofs"])[2]
			acc[1] += 1
	return [(batch, acc[0] / acc[1]) for batch, acc in sorted(sums.items())]


def q6(json_root: Path) -> list[tuple]:
	out = []
	for path in _files(json_root):
		doc = _scan(path)
		if doc is None:
			continue
		load = doc.tracks[0]["load_mn"]
		out.append((doc.coupon_code, load, _mean([t["wear"] for t in doc.tracks])))
	return out


def q7(json_root: Path) -> list[tuple]:
	groups: dict[tuple[str, int], list[float]] = {}
	for path in _files(json_root):
		doc = _scan(path)
		if doc is None:
			continue
		for track in doc.tracks:
			bucket = int(round(track["speed_mm_s"] * track["load_mn"]))
			run_in = summarize_track(track["cofs"])[2]
			acc = groups.setdefault((track["environment"], bucket), [0.0, 0.0])
			for cof in track["cofs"][run_in:]:
				acc[0] += cof
				acc[1] += 1
	return [(env, bucket, acc[0] / acc[1]) for (env, bucket), acc in sorted(groups.items())]
