from __future__ import annotations

import csv
import hashlib
import io
import urllib.request
from typing import Any

import numpy as np


ZENODO_RECORD = "https://doi.org/10.5281/zenodo.11102543"

SRF_SOURCES: dict[str, dict[str, Any]] = {
    "green": {
        "file": "Mavic3-MS_G_filter.csv",
        "url": "https://zenodo.org/api/records/11102543/files/Mavic3-MS_G_filter.csv/content",
        "sha256": "444ca41b94f60f24686281658b510d1470f19a9c555263ebbf91e5a1f73680d8",
        "nominalCenterNm": 560.0,
        "nominalFwhmNm": 32.0,
    },
    "red": {
        "file": "Mavic3-MS_R_filter.csv",
        "url": "https://zenodo.org/api/records/11102543/files/Mavic3-MS_R_filter.csv/content",
        "sha256": "fb713558557b3d984f6dc2a2789e6c17bc161cc5b4d89c9236248569ad3f6fa9",
        "nominalCenterNm": 650.0,
        "nominalFwhmNm": 32.0,
    },
    "red_edge": {
        "file": "Mavic3-MS_RE_filter.csv",
        "url": "https://zenodo.org/api/records/11102543/files/Mavic3-MS_RE_filter.csv/content",
        "sha256": "124270bb5bd99d35e77442597eb623d653697249702d816ed5faf449fd1a8630",
        "nominalCenterNm": 730.0,
        "nominalFwhmNm": 32.0,
    },
    "nir": {
        "file": "Mavic3-MS_IR_filter.csv",
        "url": "https://zenodo.org/api/records/11102543/files/Mavic3-MS_IR_filter.csv/content",
        "sha256": "f85c0e2dbdd72a279cf55d9ec8d4547a92e60faf8cd4718089de3f15acf9cc43",
        "nominalCenterNm": 860.0,
        "nominalFwhmNm": 52.0,
    },
}


def parse_response_csv(payload: bytes) -> tuple[np.ndarray, np.ndarray]:
    rows = list(csv.reader(io.StringIO(payload.decode("utf-8-sig"))))
    values = [(float(row[0]), float(row[1])) for row in rows[1:] if len(row) >= 2]
    if len(values) < 3:
        raise ValueError("spectral response requires at least three rows")
    wavelength = np.asarray([row[0] for row in values], dtype=float)
    response = np.asarray([row[1] for row in values], dtype=float)
    if not np.all(np.diff(wavelength) > 0):
        raise ValueError("wavelengths must be strictly increasing")
    if np.any(response < 0) or float(response.max()) <= 0:
        raise ValueError("response must be non-negative and non-zero")
    return wavelength, response / float(response.max())


def _half_max_crossing(x: np.ndarray, y: np.ndarray, rising: bool) -> float:
    half = 0.5
    if rising:
        candidates = np.where((y[:-1] < half) & (y[1:] >= half))[0]
        index = int(candidates[0])
    else:
        candidates = np.where((y[:-1] >= half) & (y[1:] < half))[0]
        index = int(candidates[-1])
    x0, x1 = float(x[index]), float(x[index + 1])
    y0, y1 = float(y[index]), float(y[index + 1])
    return x0 + (half - y0) * (x1 - x0) / (y1 - y0)


def summarize_response(wavelength: np.ndarray, normalized_response: np.ndarray) -> dict[str, float]:
    area = float(np.trapezoid(normalized_response, wavelength))
    if area <= 0:
        raise ValueError("spectral response area must be positive")
    effective = float(np.trapezoid(wavelength * normalized_response, wavelength) / area)
    left = _half_max_crossing(wavelength, normalized_response, rising=True)
    right = _half_max_crossing(wavelength, normalized_response, rising=False)
    return {
        "effectiveWavelengthNm": round(effective, 3),
        "peakWavelengthNm": round(float(wavelength[int(np.argmax(normalized_response))]), 3),
        "fwhmNm": round(right - left, 3),
        "halfMaxLowerNm": round(left, 3),
        "halfMaxUpperNm": round(right, 3),
        "sampleCount": int(len(wavelength)),
    }


def fetch_and_analyse_responses(timeout_s: float = 30.0) -> tuple[list[dict[str, Any]], dict[str, tuple[np.ndarray, np.ndarray]]]:
    rows: list[dict[str, Any]] = []
    curves: dict[str, tuple[np.ndarray, np.ndarray]] = {}
    for band, source in SRF_SOURCES.items():
        request = urllib.request.Request(source["url"], headers={"User-Agent": "SHWRP-2026-034/1.1"})
        with urllib.request.urlopen(request, timeout=timeout_s) as response:
            payload = response.read()
        digest = hashlib.sha256(payload).hexdigest()
        if digest != source["sha256"]:
            raise ValueError(f"source hash mismatch for {band}")
        wavelength, response = parse_response_csv(payload)
        summary = summarize_response(wavelength, response)
        rows.append({
            "band": band,
            "nominalCenterNm": source["nominalCenterNm"],
            "nominalFwhmNm": source["nominalFwhmNm"],
            **summary,
            "effectiveMinusNominalNm": round(summary["effectiveWavelengthNm"] - source["nominalCenterNm"], 3),
            "sourceDoi": ZENODO_RECORD,
            "sourceFile": source["file"],
            "sourceSha256": digest,
            "sourceLicense": "CC BY 4.0",
            "rawFileRedistributed": False,
        })
        curves[band] = (wavelength, response)
    return rows, curves

