"""
Exercise 2: asyncio.gather for real concurrency (medium)

STEP 1: Implement `fetch(name, delay)` — an async function that awaits
        `asyncio.sleep(delay)`, then returns f"{name} done".

STEP 2: Implement `run_sequential(jobs)` — `jobs` is a list of (name, delay)
        tuples. Await `fetch` for each job ONE AT A TIME, in order,
        collecting results into a list, and return that list.

STEP 3: Implement `run_concurrent(jobs)` — same input shape, but use
        `asyncio.gather(*coros)` to run every job's `fetch` call
        concurrently instead of one at a time, and return the list of
        results (gather preserves input order regardless of completion
        order).

The self-check proves run_concurrent is actually faster than
run_sequential for the same jobs by timing both with
time.perf_counter() and checking concurrent finishes in well under the
sum of the individual delays.
"""

import asyncio
import time


async def fetch(name, delay):
    # TODO: await asyncio.sleep(delay), then return f"{name} done"
    pass


async def run_sequential(jobs):
    results = []
    # TODO: for each (name, delay) in jobs, await fetch(name, delay) and
    # append the result, one job fully finishing before the next starts
    return results


async def run_concurrent(jobs):
    # TODO: build a list of fetch(name, delay) coroutines (do NOT await
    # them yet), then `return await asyncio.gather(*coros)`
    pass


# --- self-check: do not modify below this line ---
checks = []

JOBS = [("a", 0.1), ("b", 0.1), ("c", 0.1)]

seq_start = time.perf_counter()
seq_result = asyncio.run(run_sequential(JOBS))
seq_elapsed = time.perf_counter() - seq_start
checks.append(("run_sequential returns correct results in order",
                seq_result, ["a done", "b done", "c done"]))
checks.append(("run_sequential takes roughly the SUM of delays (>= 0.25s)",
                seq_elapsed >= 0.25, True))

conc_start = time.perf_counter()
conc_result = asyncio.run(run_concurrent(JOBS))
conc_elapsed = time.perf_counter() - conc_start
checks.append(("run_concurrent returns correct results in the SAME order as input",
                conc_result, ["a done", "b done", "c done"]))
checks.append(("run_concurrent takes roughly the MAX of delays, well under sequential (< 0.25s)",
                conc_elapsed < 0.25, True))

passed = 0
for label, actual, expected in checks:
    ok = actual == expected
    passed += ok
    print(f"{'PASS' if ok else 'FAIL'}: {label} (got {actual!r}, expected {expected!r})")

print("PASS" if passed == len(checks) else "FAIL")
