#!/usr/bin/env python3
import argparse
import csv
import json
import math
import os
from datetime import datetime, timezone
from pathlib import Path

import numpy as np

ROOT = Path("/var/www/vps2.happyuser.info/dex")
ENV_PATH = ROOT / "DEX_REPORTS/live_check_manual_25/current_position.env"
NPZ_PATH = ROOT / "DEX_DATA/fast_npz/base_CHECK_USDC_2PCT_fresh_fee_replay_v2.npz"
ENTRY_BACKUP_NPZ_PATH = (
    ROOT
    / "DEX_DATA/fast_npz/base_CHECK_USDC_2PCT_fresh_fee_replay_v2.npz.bak_20260519T091938Z"
)
ENTRY_CURVE_CSV_PATH = (
    ROOT / "DEX_REPORTS/live_check_manual_25/check_live_profit_curve_20260517_updated.csv"
)
OUT_DIR = ROOT / "DEX_REPORTS/live_check_manual_25"
OUT_PATH = OUT_DIR / "lp_estimates.jsonl"
OUT_DIR.mkdir(parents=True, exist_ok=True)


def read_env(path):
    d = {}
    for line in path.read_text().splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, v = line.split("=", 1)
        d[k.strip()] = v.strip()
    return d


def parse_dt(value):
    if not value:
        return None
    text = str(value).strip()
    try:
        if text.endswith("Z"):
            text = text[:-1] + "+00:00"
        dt = datetime.fromisoformat(text)
    except ValueError:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(timezone.utc)


def unix_to_dt(value):
    try:
        return datetime.fromtimestamp(float(value), tz=timezone.utc)
    except (TypeError, ValueError, OSError):
        return None


def first_curve_row(path):
    if not path.exists():
        return None
    with path.open(newline="") as f:
        reader = csv.DictReader(f)
        for row in reader:
            return row
    return None


def first_jsonl_row(path):
    if not path.exists():
        return None
    with path.open() as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            return json.loads(line)
    return None


def npz_price_nearest(path, target_dt):
    if not path.exists():
        return None

    z = np.load(path, allow_pickle=True)
    prices = np.asarray(z["price"], dtype=float)
    if len(prices) == 0:
        return None

    if target_dt is None or "ts" not in z.files:
        idx = 0
    else:
        ts = np.asarray(z["ts"], dtype=float)
        target_ts = target_dt.timestamp()
        idx = int(np.argmin(np.abs(ts - target_ts)))

    return {
        "price": float(prices[idx]),
        "source": str(path.relative_to(ROOT)),
        "source_index": idx,
        "source_ts_utc": unix_to_dt(z["ts"][idx]).isoformat()
        if "ts" in z.files and len(z["ts"]) > idx
        else None,
    }


def resolve_entry_price():
    curve_row = first_curve_row(ENTRY_CURVE_CSV_PATH)
    entry_dt = parse_dt(curve_row.get("ts_utc")) if curve_row else None

    backup_price = npz_price_nearest(ENTRY_BACKUP_NPZ_PATH, entry_dt)
    if backup_price:
        backup_price["source_reason"] = "backup_npz_nearest_entry_time"
        return backup_price

    if curve_row and curve_row.get("price_usdc_per_check"):
        return {
            "price": float(curve_row["price_usdc_per_check"]),
            "source": str(ENTRY_CURVE_CSV_PATH.relative_to(ROOT)),
            "source_ts_utc": parse_dt(curve_row.get("ts_utc")).isoformat()
            if parse_dt(curve_row.get("ts_utc"))
            else curve_row.get("ts_utc"),
            "source_reason": "curve_csv_first_entry_row",
        }

    first_estimate = first_jsonl_row(OUT_PATH)
    if first_estimate:
        price = first_estimate.get("entry_price_usdc_per_check")
        if price is None:
            price = first_estimate.get("price_usdc_per_check")
        if price is not None:
            return {
                "price": float(price),
                "source": str(OUT_PATH.relative_to(ROOT)),
                "source_ts_utc": first_estimate.get("ts_utc"),
                "source_reason": "first_lp_estimate_fallback",
            }

    raise RuntimeError("could not resolve entry price from backup NPZ, curve CSV, or lp_estimates.jsonl")


def resolve_current_price(args):
    if args.current_price_usdc_per_check is not None:
        return float(args.current_price_usdc_per_check), "arg_override"

    env_override = os.environ.get("CHECK_LIVE_CURRENT_PRICE_USDC_PER_CHECK")
    if env_override:
        return float(env_override), "env_CHECK_LIVE_CURRENT_PRICE_USDC_PER_CHECK"

    z = np.load(NPZ_PATH, allow_pickle=True)
    prices = np.asarray(z["price"], dtype=float)
    if len(prices) == 0:
        raise RuntimeError(f"current NPZ has no price rows: {NPZ_PATH}")
    return float(prices[-1]), str(NPZ_PATH.relative_to(ROOT))


def liquidity_from_deposit(deposited_usdc, deposited_check, price_check_per_usdc, pa, pb):
    sqrt_pa = math.sqrt(pa)
    sqrt_pb = math.sqrt(pb)
    sqrt_p = math.sqrt(price_check_per_usdc)

    if pa < price_check_per_usdc < pb:
        l_from_usdc = deposited_usdc * sqrt_p * sqrt_pb / (sqrt_pb - sqrt_p)
        l_from_check = deposited_check / (sqrt_p - sqrt_pa)
        return min(l_from_usdc, l_from_check), "IN_RANGE_MIN_DEPOSIT_SIDE"
    if price_check_per_usdc <= pa:
        return deposited_check / (sqrt_pb - sqrt_pa), "ENTRY_OUT_BELOW_RANGE_ALL_CHECK"
    return deposited_usdc * sqrt_pa * sqrt_pb / (sqrt_pb - sqrt_pa), "ENTRY_OUT_ABOVE_RANGE_ALL_USDC"


def amounts_for_price(liquidity, price_check_per_usdc, pa, pb):
    sqrt_pa = math.sqrt(pa)
    sqrt_pb = math.sqrt(pb)
    sqrt_p = math.sqrt(price_check_per_usdc)

    if price_check_per_usdc <= pa:
        amount0_usdc = 0.0
        amount1_check = liquidity * (sqrt_pb - sqrt_pa)
        status = "OUT_BELOW_RANGE_ALL_CHECK"
    elif price_check_per_usdc >= pb:
        amount0_usdc = liquidity * (sqrt_pb - sqrt_pa) / (sqrt_pa * sqrt_pb)
        amount1_check = 0.0
        status = "OUT_ABOVE_RANGE_ALL_USDC"
    else:
        amount0_usdc = liquidity * (sqrt_pb - sqrt_p) / (sqrt_p * sqrt_pb)
        amount1_check = liquidity * (sqrt_p - sqrt_pa)
        status = "IN_RANGE"

    return amount0_usdc, amount1_check, status


def build_arg_parser():
    parser = argparse.ArgumentParser(description="Read-only CHECK/USDC live LP estimator")
    parser.add_argument(
        "--current-price-usdc-per-check",
        type=float,
        default=None,
        help="Override current CHECK price in USDC; default is current fresh NPZ last price.",
    )
    return parser


def main():
    args = build_arg_parser().parse_args()
    env = read_env(ENV_PATH)

    deposited_usdc = float(env["DEPOSITED_USDC"])
    deposited_check = float(env["DEPOSITED_CHECK"])
    range_low = float(env["RANGE_LOW_CHECK_PER_USDC"])
    range_high = float(env["RANGE_HIGH_CHECK_PER_USDC"])

    entry_price_info = resolve_entry_price()
    entry_price_usdc_per_check = entry_price_info["price"]
    current_price_usdc_per_check, current_price_source = resolve_current_price(args)

    entry_price_check_per_usdc = 1.0 / entry_price_usdc_per_check
    current_price_check_per_usdc = 1.0 / current_price_usdc_per_check

    explicit_initial = env.get("INITIAL_VALUE_USD") or env.get("DEPOSIT_TOTAL_USD_EST")
    if explicit_initial is not None:
        initial_value_usd = float(explicit_initial)
        initial_value_source = "current_position.env"
    else:
        initial_value_usd = deposited_usdc + deposited_check * entry_price_usdc_per_check
        initial_value_source = "deposited_usdc_plus_deposited_check_times_entry_price"

    liquidity, liquidity_source = liquidity_from_deposit(
        deposited_usdc,
        deposited_check,
        entry_price_check_per_usdc,
        range_low,
        range_high,
    )
    amount_usdc, amount_check, range_status = amounts_for_price(
        liquidity,
        current_price_check_per_usdc,
        range_low,
        range_high,
    )

    position_value_usd = amount_usdc + amount_check * current_price_usdc_per_check
    price_only_pnl_usd = position_value_usd - initial_value_usd
    price_only_pnl_pct = price_only_pnl_usd / initial_value_usd * 100.0

    hodl_value_usd = deposited_usdc + deposited_check * current_price_usdc_per_check
    lp_vs_hodl_usd = position_value_usd - hodl_value_usd

    row = {
        "ts_utc": datetime.now(timezone.utc).isoformat(),
        "tx_hash": env.get("TX_HASH"),
        "position_id": env.get("POSITION_ID"),
        "wallet": env.get("WALLET"),
        "entry_price_usdc_per_check": entry_price_usdc_per_check,
        "entry_price_check_per_usdc": entry_price_check_per_usdc,
        "entry_price_source": entry_price_info,
        "current_price_usdc_per_check": current_price_usdc_per_check,
        "current_price_check_per_usdc": current_price_check_per_usdc,
        "current_price_source": current_price_source,
        "range_low_check_per_usdc": range_low,
        "range_high_check_per_usdc": range_high,
        "range_status": range_status,
        "estimated_amount_usdc": amount_usdc,
        "estimated_amount_check": amount_check,
        "liquidity_estimate": liquidity,
        "liquidity_source": liquidity_source,
        "initial_value_usd": initial_value_usd,
        "initial_value_source": initial_value_source,
        "estimated_position_value_usd_ex_fees": position_value_usd,
        "price_only_pnl_usd_ex_fees": price_only_pnl_usd,
        "price_only_pnl_pct_ex_fees": price_only_pnl_pct,
        "hodl_value_usd": hodl_value_usd,
        "lp_vs_hodl_usd_ex_fees": lp_vs_hodl_usd,
        "note": "Read-only estimate. Fees are not included.",
    }

    print(json.dumps(row, indent=2))

    with OUT_PATH.open("a") as f:
        f.write(json.dumps(row) + "\n")


if __name__ == "__main__":
    main()
