import httpx
import csv
import io
import time
from datetime import datetime, date
import os

# ===============================
# CONFIGURACIÓN
# ===============================
BASE = "http://localhost:25503/v3"
SYMBOL = "SPXW"
MAX_STRIKES = 120         # cantidad de strikes a capturar
INTERVAL_SECONDS = 10      # refresco
# ===============================


def get_today_expiration():
    return date.today().strftime("%Y-%m-%d")


def get_underlying_price():
    url = f"{BASE}/option/snapshot/greeks/first_order"
    params = {"symbol": SYMBOL, "expiration": "*", "strike": "*"}

    with httpx.stream("GET", url, params=params, timeout=10) as r:
        for line in r.iter_lines():
            try:
                row = next(csv.reader(io.StringIO(line)))
                return float(row[-1])
            except Exception:
                continue

    return None


def get_strikes(expiration):
    url = f"{BASE}/option/list/strikes"
    params = {"symbol": SYMBOL, "expiration": expiration}

    strikes = []

    with httpx.stream("GET", url, params=params, timeout=10) as r:
        for line in r.iter_lines():
            try:
                row = next(csv.reader(io.StringIO(line)))
                strikes.append(float(row[1]))
            except Exception:
                continue

    return sorted(strikes)


def get_snapshot_quotes(expiration, strikes_subset):
    url = f"{BASE}/option/snapshot/quote"
    params = {
        "symbol": SYMBOL,
        "expiration": expiration,
        "strike": "*",
        "right": "both",
    }

    snapshot_rows = []

    with httpx.stream("GET", url, params=params, timeout=10) as r:
        for line in r.iter_lines():
            try:
                row = next(csv.reader(io.StringIO(line)))
                if row[0] == "timestamp":
                    continue

                strike = float(row[3])
                if strike in strikes_subset:
                    snapshot_rows.append(row)
            except Exception:
                continue

    return snapshot_rows


def get_snapshot_greeks(expiration, strikes_subset):
    url = f"{BASE}/option/snapshot/greeks/first_order"
    params = {
        "symbol": SYMBOL,
        "expiration": expiration,
        "strike": "*",
        "right": "both",
    }

    greeks = {}

    with httpx.stream("GET", url, params=params, timeout=10) as r:
        for line in r.iter_lines():
            try:
                row = next(csv.reader(io.StringIO(line)))

                if row[0] == "symbol":
                    continue

                strike = float(row[2])
                if strike not in strikes_subset:
                    continue

                right = row[3]
                delta = float(row[7])

                if strike not in greeks:
                    greeks[strike] = {"delta_call": None, "delta_put": None}

                if right == "CALL":
                    greeks[strike]["delta_call"] = delta
                else:
                    greeks[strike]["delta_put"] = delta

            except Exception:
                continue

    return greeks


def get_snapshot_gamma(expiration, strikes_subset):
    gamma_map = {}

    for strike in strikes_subset:
        gamma_map[strike] = {
            "gamma_call": None,
            "gamma_put": None,
        }

    return gamma_map


def get_snapshot_volume(expiration, strikes_subset):
    url = f"{BASE}/option/snapshot/ohlc"
    params = {
        "symbol": SYMBOL,
        "expiration": expiration,
        "strike": "*",
        "right": "both",
    }

    volumes = {}

    with httpx.stream("GET", url, params=params, timeout=10) as r:
        for line in r.iter_lines():
            try:
                row = next(csv.reader(io.StringIO(line)))
                if row[0] == "timestamp":
                    continue

                strike = float(row[3])
                if strike not in strikes_subset:
                    continue

                right = row[4]
                volume = int(row[9])

                if strike not in volumes:
                    volumes[strike] = {"volume_call": 0, "volume_put": 0}

                if right == "CALL":
                    volumes[strike]["volume_call"] = volume
                else:
                    volumes[strike]["volume_put"] = volume

            except Exception:
                continue

    return volumes


def get_snapshot_open_interest(expiration, strikes_subset):
    url = f"{BASE}/option/snapshot/open_interest"
    params = {
        "symbol": SYMBOL,
        "expiration": expiration,
        "strike": "*",
        "right": "both",
    }

    oi = {}

    with httpx.stream("GET", url, params=params, timeout=10) as r:
        for line in r.iter_lines():
            try:
                row = next(csv.reader(io.StringIO(line)))
                if row[0] == "timestamp":
                    continue

                strike = float(row[3])
                if strike not in strikes_subset:
                    continue

                right = row[4]
                open_interest = int(row[5])

                if strike not in oi:
                    oi[strike] = {"oi_call": 0, "oi_put": 0}

                if right == "CALL":
                    oi[strike]["oi_call"] = open_interest
                else:
                    oi[strike]["oi_put"] = open_interest

            except Exception:
                continue

    return oi


# ==========================================================
#   BUILD OPTION CHAIN CONSOLIDADO
# ==========================================================

def build_option_chain(snapshot_rows, snapshot_timestamp, underlying, greeks_map, gamma_map, volume_map, oi_map):
    chain = {}

    for row in snapshot_rows:
        strike = float(row[3])
        right = row[4]
        bid = float(row[7])
        ask = float(row[11])

        if strike not in chain:
            chain[strike] = {
                "timestamp": snapshot_timestamp,
                "underlying_price": underlying,
                "strike": strike,
                "bid_call": None,
                "ask_call": None,
                "bid_put": None,
                "ask_put": None,
                "delta_call": None,
                "delta_put": None,
                "gamma_call": None,
                "gamma_put": None,
                "volume_call": None,
                "volume_put": None,
                "oi_call": None,
                "oi_put": None,
            }

        if right == "CALL":
            chain[strike]["bid_call"] = bid
            chain[strike]["ask_call"] = ask
        else:
            chain[strike]["bid_put"] = bid
            chain[strike]["ask_put"] = ask

    for strike in chain:
        if strike in greeks_map:
            chain[strike]["delta_call"] = greeks_map[strike]["delta_call"]
            chain[strike]["delta_put"] = greeks_map[strike]["delta_put"]
        if strike in gamma_map:
            chain[strike]["gamma_call"] = gamma_map[strike]["gamma_call"]
            chain[strike]["gamma_put"] = gamma_map[strike]["gamma_put"]
        if strike in volume_map:
            chain[strike]["volume_call"] = volume_map[strike]["volume_call"]
            chain[strike]["volume_put"] = volume_map[strike]["volume_put"]
        if strike in oi_map:
            chain[strike]["oi_call"] = oi_map[strike]["oi_call"]
            chain[strike]["oi_put"] = oi_map[strike]["oi_put"]

    return sorted(chain.values(), key=lambda x: x["strike"])


def fmt(value):
    return "" if value is None else f"{value:.4f}" if isinstance(value, float) else str(value)


def print_option_chain(chain):
    print("\n================== OPTION CHAIN CONSOLIDADA ==================")
    print("strike | bidC   askC   | bidP   askP   | dC        dP        gC        gP        vC     vP     oC     oP")

    for c in chain:
        print(
            f"{int(c['strike']):5d}  "
            f"{fmt(c['bid_call']):>6} {fmt(c['ask_call']):>6}   "
            f"{fmt(c['bid_put']):>6} {fmt(c['ask_put']):>6}   "
            f"{fmt(c['delta_call']):>8} {fmt(c['delta_put']):>8}  "
            f"{fmt(c['gamma_call']):>8} {fmt(c['gamma_put']):>8}  "
            f"{fmt(c['volume_call']):>5} {fmt(c['volume_put']):>5}  "
            f"{fmt(c['oi_call']):>5} {fmt(c['oi_put']):>5}"
        )


def save_chain_csv(chain):
    today = date.today().strftime("%Y-%m-%d")
    filename = f"optionChain_SPX_{today}.csv"

    file_exists = os.path.isfile(filename)

    with open(filename, "a", newline="") as f:
        writer = csv.writer(f)

        if not file_exists:
            writer.writerow([
                "timestamp", "underlying_price", "strike",
                "bid_call", "ask_call", "bid_put", "ask_put",
                "delta_call", "delta_put",
                "gamma_call", "gamma_put",
                "volume_call", "volume_put",
                "oi_call", "oi_put",
            ])

        for row in chain:
            writer.writerow([
                row["timestamp"],
                row["underlying_price"],
                row["strike"],
                row["bid_call"],
                row["ask_call"],
                row["bid_put"],
                row["ask_put"],
                row["delta_call"],
                row["delta_put"],
                row["gamma_call"],
                row["gamma_put"],
                row["volume_call"],
                row["volume_put"],
                row["oi_call"],
                row["oi_put"],
            ])

    print(f"CSV actualizado → {filename}")


# ==========================================================
#                MAIN LOOP
# ==========================================================

def run_loop():
    print("=== SPXW 0DTE REAL-TIME SNAPSHOT (ThetaData) ===")

    expiration = get_today_expiration()
    print(f"Usando expiración 0DTE: {expiration}")

    while True:
        now_str = datetime.now().strftime("%H:%M:%S")
        print(f"\n[{now_str}] Actualizando snapshot...")

        underlying = get_underlying_price()
        if underlying is None:
            print("⚠ No se pudo obtener el precio subyacente SPX.")
            time.sleep(INTERVAL_SECONDS)
            continue

        print(f"SPX Underlying: {underlying}")

        strikes = get_strikes(expiration)
        if not strikes:
            print(f"⚠ No hay strikes disponibles hoy ({expiration})")
            time.sleep(INTERVAL_SECONDS)
            continue

        strikes_sorted = sorted(strikes, key=lambda x: abs(x - underlying))
        strikes_selected = strikes_sorted[:MAX_STRIKES]
        print(f"Strikes seleccionados: {len(strikes_selected)}")

        snapshot = get_snapshot_quotes(expiration, strikes_selected)
        greeks_map = get_snapshot_greeks(expiration, strikes_selected)
        gamma_map = get_snapshot_gamma(expiration, strikes_selected)
        volume_map = get_snapshot_volume(expiration, strikes_selected)
        oi_map = get_snapshot_open_interest(expiration, strikes_selected)

        snapshot_ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        option_chain = build_option_chain(snapshot, snapshot_ts, underlying, greeks_map, gamma_map, volume_map, oi_map)

        if snapshot:
            print(f"Filas recibidas: {len(snapshot)}")
            print_option_chain(option_chain)
            save_chain_csv(option_chain)
        else:
            print("⚠ No hubo datos de snapshot.")

        time.sleep(INTERVAL_SECONDS)


if __name__ == "__main__":
    run_loop()