"""Read-only chart projections from the SAME accepted records as the KPI engine."""
from datetime import datetime, timedelta, time
from decimal import Decimal
from monthly_engine import ROME, timestamp, amount, comparable_end, build_report


def curve(records, start, end):
    """Observed closed-PnL only. Initial zero is part of the high-water mark."""
    running=peak=Decimal(0)
    points=[{'date':start,'net':'0','drawdown':'0'}]
    for row in sorted(records,key=lambda r:(timestamp(r['closed_at']),r['order_id'])):
        running+=amount(row['pnl_net'])
        peak=max(peak,running)
        points.append({'date':row['closed_at'],'net':str(running),'drawdown':str(running-peak)})
    if points[-1]['date']!=end:
        points.append({'date':end,'net':str(running),'drawdown':str(running-peak)})
    return {'points':points,'max_drawdown_usdt':str(-min(amount(p['drawdown']) for p in points)),
            'drawdown_pct':None,'basis':'observed_closed_pnl_no_funding_no_mark_to_market'}


def days(records, period, source):
    start,end=timestamp(period['start_inclusive']),timestamp(period['as_of_exclusive'])
    cov_start,cov_end=timestamp(source['coverage_start']),timestamp(source['coverage_end'])
    buckets={}
    for row in records:
        key=timestamp(row['closed_at']).astimezone(ROME).date().isoformat()
        bucket=buckets.setdefault(key,{'net':Decimal(0),'n_trades':0})
        bucket['net']+=amount(row['pnl_net']);bucket['n_trades']+=1
    day=start.astimezone(ROME).date()
    result=[]
    while datetime.combine(day,time(),ROME)<end:
        left=datetime.combine(day,time(),ROME)
        right=min(datetime.combine(day+timedelta(days=1),time(),ROME),end)
        covered=cov_start<=left and cov_end>=right
        row=buckets.get(day.isoformat(),{'net':Decimal(0),'n_trades':0})
        result.append({'date':day.isoformat(),'net':str(row['net']), 'n_trades':row['n_trades'],
            'status':'complete' if covered and period['data_status']=='complete' else 'incomplete',
            'fully_covered':covered,'has_observations':bool(row['n_trades'])})
        day+=timedelta(days=1)
    return result


def comparison_periods(report, alignment):
    if alignment not in {'full','equal'}:raise ValueError('Invalid comparison alignment')
    if alignment=='full':return report['months']
    rows=[{**r,'updated_at':r['closed_at'],'classification_status':'classified'} for r in report['records'].values()]
    source=report['source']
    result={}
    for month in report['months']:
        cutoff=min(comparable_end(month,report['as_of']),timestamp(report['as_of']))
        cut=build_report([r for r in rows if timestamp(r['closed_at'])<cutoff],selected_month=month,as_of=cutoff,
            coverage_start=source['coverage_start'],coverage_end=source['coverage_end'],
            source_complete=source['complete_asserted'],stale=source['stale'],
            source_name=source['name'],history_start_month=min(report['months']))
        result[month]=cut['selected']
    return result


def charts(report, period, compared):
    records=report['records']
    selected=[records[oid] for oid in period['trade_ids']]
    overlay=[]
    for month,item in compared.items():
        subset=[records[oid] for oid in item['trade_ids']]
        projection=curve(subset,item['start_inclusive'],item['as_of_exclusive'])
        start_local=timestamp(item['start_inclusive']).astimezone(ROME)
        for p in projection['points']:
            local=timestamp(p['date']).astimezone(ROME)
            # Calendar progression, not elapsed 24h days: robust across DST.
            p['day']=float((local.replace(tzinfo=None)-start_local.replace(tzinfo=None)).total_seconds()/86400)
        overlay.append({'month':month,'cutoff':item['as_of_exclusive'],'data_status':item['data_status'],
                        'n_trades':item['kpis']['n_trades'],**projection})
    return {'period':curve(selected,period['start_inclusive'],period['as_of_exclusive']),
            'daily':days(selected,period,report['source']),'overlay':overlay}


def extra_kpis(report, period, details, snapshot):
    k=period['kpis'];n=k['n_trades']
    if details:
        first=min(timestamp(row['closed_at']) for row in details)
        legacy_days=max(1,(timestamp(period['as_of_exclusive'])-first).days+1)
        first_trade_date=first.astimezone(ROME).strftime('%Y-%m-%d')
    else:legacy_days=0;first_trade_date=None
    divide=lambda value: str(amount(value)/legacy_days) if legacy_days else None
    values=[amount(t['pnl_net']) for t in details]
    losses=-sum((v for v in values if v<0),Decimal(0))
    notional=Decimal(0);missing=0
    for row in details:
        try:
            qty,entry=amount(row.get('qty')),amount(row.get('entry'))
            if qty<=0 or entry<=0:raise ValueError('invalid notional input')
            notional+=abs(qty*entry)
        except ValueError:missing+=1
    all_records=list(report['records'].values())
    observed_total=sum((amount(r['pnl_net']) for r in all_records),Decimal(0))
    reconstructed_base=amount(snapshot['kpis']['balance'])-observed_total
    observed_before_period=sum((amount(r['pnl_net']) for r in all_records
                                if timestamp(r['closed_at'])<timestamp(period['start_inclusive'])),Decimal(0))
    observed_to_end=sum((amount(r['pnl_net']) for r in all_records
                        if timestamp(r['closed_at'])<timestamp(period['as_of_exclusive'])),Decimal(0))
    return {'observed_capital_at_end':str(Decimal(4000)+observed_to_end),
        'capital_status':'illustrative_partial_history_not_certified',
        'win_loss_ratio':str(Decimal(k['wins'])/k['losses']) if k['losses'] else None,
        'win_loss_state':'finite' if k['losses'] else ('infinite' if k['wins'] else 'undefined'),
        'losses_usdt':str(losses),
        'legacy_gross_display':str(amount(k['pnl_net'])+losses+amount(k['fees'])),
        'legacy_trading_days':legacy_days,
        'first_trade_date':first_trade_date,
        'equity_before_period':str(reconstructed_base+observed_before_period),
        'equity_at_period_end':str(reconstructed_base+observed_to_end),
        'equity_basis':'dash_v2_reconstructed_from_snapshot_balance',
        'trades_per_day':divide(n),'notional_per_day':divide(notional) if not missing else None,
        'notional_missing':missing,'day_profit_pct':str(amount(k['day_profit_calendar'])/40) if k['day_profit_calendar'] is not None else None,
        'snapshot_unrealized':str(snapshot['kpis']['unrealized_pnl']),
        'snapshot_total_pnl':str(snapshot['kpis']['total_pnl_with_unrealized']),
        'snapshot_open_positions':snapshot['kpis']['open_positions']}
