#!/usr/bin/env python3
"""Shared TradingView daily-bar outcome helpers for active screeners."""

import json
import math
import urllib.request

from neobdm_common import WIB, datetime
from tradingview_history import load_tradingview_headers


DAILY_PERIODS = 20
TRADING_COST_PCT = 0.30
PATH_TARGET_PCT = 2.0
PATH_STOP_PCT = -2.0
TV_SCAN_URL = "https://scanner.tradingview.com/indonesia/scan"


def _finite(value, default=None):
    try:
        number = float(value)
    except (TypeError, ValueError):
        return default
    return number if math.isfinite(number) else default


def _daily_columns(periods):
    columns = []
    for offset in range(periods):
        suffix = "" if offset == 0 else f"[{offset}]"
        columns.extend([f"time{suffix}", f"open{suffix}", f"high{suffix}", f"low{suffix}", f"close{suffix}"])
    return columns


def fetch_daily_bars(tickers, periods=DAILY_PERIODS, chunk_size=100):
    """Fetch dated TradingView daily bars without fabricating calendar dates."""
    symbols = sorted({str(ticker).replace(".JK", "").upper() for ticker in tickers if str(ticker).strip()})
    if not symbols:
        return {}
    columns = _daily_columns(periods)
    headers = load_tradingview_headers()
    output = {}
    for start in range(0, len(symbols), chunk_size):
        chunk = symbols[start:start + chunk_size]
        request = urllib.request.Request(
            TV_SCAN_URL,
            data=json.dumps({
                "symbols": {"tickers": [f"IDX:{ticker}" for ticker in chunk], "query": {"types": []}},
                "columns": columns,
            }).encode("utf-8"),
            headers=headers,
        )
        with urllib.request.urlopen(request, timeout=25) as response:
            payload = json.loads(response.read().decode("utf-8"))
        for item in payload.get("data") or []:
            ticker = str(item.get("s") or "").split(":")[-1].upper()
            values = item.get("d") or []
            bars_by_date = {}
            for offset in range(periods):
                index = offset * 5
                if len(values) <= index + 4:
                    break
                timestamp, open_price, high, low, close = values[index:index + 5]
                numbers = [_finite(value) for value in (timestamp, open_price, high, low, close)]
                if any(value is None for value in numbers):
                    continue
                date = datetime.fromtimestamp(numbers[0], tz=WIB).strftime("%Y-%m-%d")
                bars_by_date[date] = {"date": date, "open": numbers[1], "high": numbers[2], "low": numbers[3], "close": numbers[4]}
            output[ticker] = [bars_by_date[date] for date in sorted(bars_by_date)]
    return output


def _return_pct(price, entry):
    return round((price - entry) / entry * 100, 4)


def resolve_observation(observation, bars):
    entry = _finite(observation.get("entry_price"))
    future = [bar for bar in bars if str(bar.get("date") or "") > str(observation.get("date") or "")]
    result = {
        **observation,
        "source": "TradingView daily bars",
        "status": "PENDING",
        "d1_session_date": None,
        "d3_session_date": None,
        "d1_close_return_pct": None,
        "d1_net_return_pct": None,
        "d3_close_return_pct": None,
        "d3_net_return_pct": None,
        "mfe_d3_pct": None,
        "mae_d3_pct": None,
        "path_2pct": "PENDING",
        "label_d1": None,
        "label_d3": None,
    }
    if not entry or entry <= 0 or not future:
        return result
    d1 = future[0]
    d1_return = _return_pct(_finite(d1.get("close"), entry), entry)
    result.update({
        "status": "D1_RESOLVED",
        "d1_session_date": d1["date"],
        "d1_close_return_pct": d1_return,
        "d1_net_return_pct": round(d1_return - TRADING_COST_PCT, 4),
        "label_d1": 1 if d1_return > 0 else 0,
    })
    window = future[:3]
    result["mfe_d3_pct"] = max(_return_pct(_finite(bar.get("high"), entry), entry) for bar in window)
    result["mae_d3_pct"] = min(_return_pct(_finite(bar.get("low"), entry), entry) for bar in window)
    for bar in window:
        high_return = _return_pct(_finite(bar.get("high"), entry), entry)
        low_return = _return_pct(_finite(bar.get("low"), entry), entry)
        target_hit = high_return >= PATH_TARGET_PCT
        stop_hit = low_return <= PATH_STOP_PCT
        if target_hit and stop_hit:
            result["path_2pct"] = "AMBIGUOUS_SAME_DAILY_BAR"
            break
        if target_hit:
            result["path_2pct"] = "TARGET_FIRST"
            break
        if stop_hit:
            result["path_2pct"] = "STOP_FIRST"
            break
    else:
        result["path_2pct"] = "UNRESOLVED" if len(window) == 3 else "PENDING"
    if len(future) >= 3:
        d3 = future[2]
        d3_return = _return_pct(_finite(d3.get("close"), entry), entry)
        result.update({
            "status": "D3_RESOLVED",
            "d3_session_date": d3["date"],
            "d3_close_return_pct": d3_return,
            "d3_net_return_pct": round(d3_return - TRADING_COST_PCT, 4),
            "label_d3": 1 if d3_return > 0 else 0,
        })
    return result
