from __future__ import annotations

import math
from pathlib import Path
import numpy as np
import pandas as pd

import sys
sys.path.insert(0, '/mnt/data')
import kartsell_v12_research as base

ROOT = Path('/mnt/data')
OUT = ROOT / 'kartsell_v12_2_results'
OUT.mkdir(exist_ok=True)

KR_FILES = {
    '005930': ROOT / '005930_KR.csv',
    '000660': ROOT / '000660_KR.csv',
    '035900': ROOT / '035900_KR.csv',
}
KR_NAMES = {'005930':'Samsung Electronics','000660':'SK Hynix','035900':'JYP Ent.'}
KR_CLASS = {'005930':'large_quality_stock','000660':'cyclical_stock','035900':'high_vol_stock'}

US_FILES = base.FILES
US_CLASS = base.ASSET_CLASS


def load_kr(symbol: str, path: Path) -> pd.DataFrame:
    raw = pd.read_csv(path)
    raw.columns = [c.strip().lower() for c in raw.columns]
    raw['date'] = pd.to_datetime(raw['date'], errors='coerce')
    for c in ['open','high','low','close','volume','adj_close']:
        raw[c] = pd.to_numeric(raw[c], errors='coerce')
    raw = raw.dropna(subset=['date','open','high','low','close','adj_close']).copy()
    raw = raw[(raw['date'] >= pd.Timestamp('2007-01-01')) & (raw['date'] <= pd.Timestamp('2026-08-01'))]
    raw = raw.sort_values('date').drop_duplicates('date', keep='last').reset_index(drop=True)

    # Historical Korean Yahoo files sometimes put raw Close on a pre-split scale while OHLC is already adjusted.
    # If raw Close is inside the raw daily range, OHLC and Close share a scale and all OHLC are adjusted by adj/close.
    # If Adj_Close is inside the range but raw Close is not, the OHLC is already on the adjusted scale (notably old JYP rows).
    raw = raw[(raw['adj_close'] > 0) & (raw['open'] > 0) & (raw['high'] > 0) & (raw['low'] > 0) & (raw['close'] > 0)].copy()
    raw_in_range = (raw['close'] >= raw['low'] * .995) & (raw['close'] <= raw['high'] * 1.005)
    adj_in_range = (raw['adj_close'] >= raw['low'] * .995) & (raw['adj_close'] <= raw['high'] * 1.005)
    scale_raw = raw_in_range | (~adj_in_range)
    factor = raw['adj_close'] / raw['close']
    for c in ['open','high','low']:
        raw[c] = np.where(scale_raw, raw[c] * factor, raw[c])
    raw['close'] = raw['adj_close']

    # Infer only near-integer discontinuities above 80% as provisional corporate-action adjustments.
    # These are audit flags, not a substitute for an official corporate-action master.
    raw = raw.reset_index(drop=True)
    inferred = []
    for i in range(1, len(raw)):
        prev = float(raw.at[i-1,'close']); cur = float(raw.at[i,'close'])
        if prev <= 0 or cur <= 0:
            continue
        ratio = cur / prev
        mult = None
        if ratio >= 1.8:
            k = round(ratio)
            if 2 <= k <= 100 and abs(ratio-k)/k <= .04:
                mult = float(k)
        elif ratio <= .55:
            inv = 1.0/ratio
            k = round(inv)
            if 2 <= k <= 100 and abs(inv-k)/k <= .04:
                mult = 1.0/float(k)
        if mult is not None:
            raw.loc[:i-1, ['open','high','low','close','adj_close']] *= mult
            inferred.append((raw.at[i,'date'], mult))
    raw.attrs['inferred_actions'] = inferred
    raw['symbol'] = symbol
    return raw[['date','symbol','open','high','low','close','adj_close','volume']]


def quality(symbol: str, path: Path, df: pd.DataFrame) -> dict:
    raw = pd.read_csv(path)
    dates = pd.to_datetime(raw['Date'], errors='coerce')
    ret = df['close'].pct_change()
    invalid = ((df['high'] < df[['open','close','low']].max(axis=1)) |
               (df['low'] > df[['open','close','high']].min(axis=1))).sum()
    return {
        'symbol':symbol,'name':KR_NAMES[symbol],'market':'KOSPI' if symbol!='035900' else 'KOSDAQ',
        'start':df.date.min().date().isoformat(),'end':df.date.max().date().isoformat(),'rows':len(df),
        'raw_sorted':bool(dates.dropna().is_monotonic_increasing),'duplicate_dates':int(dates.duplicated().sum()),
        'invalid_adjusted_ohlc_rows':int(invalid),'abs_daily_return_gt_50pct':int((ret.abs()>.5).sum()),
        'zero_or_missing_volume_rows':int((df.volume.fillna(0)<=0).sum()),
        'total_return_available':False,'pit_fundamentals_available':False,
        'inferred_corporate_action_rescales': {'005930':'1:50 pre-2018-04-09','035900':'2:1 pre-2007-10-04'}.get(symbol,''),
        'source_vintage_warning':'source ends 2021-04; not current'
    }


def run_assets(dfs: dict[str,pd.DataFrame], classes: dict[str,str], market: str, cost: float):
    rows=[]; periods=[]; events=[]; bts={k:{} for k in ['buy_hold','trend_10m_shifted','trailing_12','kartsell_v11_original','kartsell_v12_candidate']}
    period_def={
        'research_2007_2013':('2007-01-01','2013-12-31'),
        'validation_2014_2017':('2014-01-01','2017-12-31'),
        'oos_2018_source_end':('2018-01-01','2026-08-01'),
        'gfc_2007_2009':('2007-01-01','2009-12-31'),
        'covid_2020':('2020-01-01','2020-12-31'),
    }
    for s,df in dfs.items():
        v11,e11=base.v11_positions(df,classes[s]); v12,e12=base.v12_positions(df,classes[s])
        posmap={'buy_hold':base.buyhold_positions(df),'trend_10m_shifted':base.trend10m_positions(df),
                'trailing_12':base.trailing_positions(df),'kartsell_v11_original':v11,'kartsell_v12_candidate':v12}
        for n,p in posmap.items():
            bt=base.backtest(df,p,cost=cost); bts[n][s]=bt
            rows.append({'market':market,'symbol':s,'strategy':n,'cost_one_way':cost,**base.metrics(bt)})
            for pn,(ps,pe) in period_def.items():
                q=base.period_slice(bt,ps,pe)
                if len(q)>=60: periods.append({'market':market,'symbol':s,'strategy':n,'period':pn,**base.metrics(q)})
        events.extend([base.event_stats(s,df,e11,'kartsell_v11_original'),base.event_stats(s,df,e12,'kartsell_v12_candidate')])
        e11.to_csv(OUT/f'{s}_v11_events.csv',index=False); e12.to_csv(OUT/f'{s}_v12_events.csv',index=False)
    return pd.DataFrame(rows),pd.DataFrame(periods),pd.DataFrame(events),bts


def portfolio_from(bts, name: str, cost: float):
    rows=[]; curves={}
    for strategy, assets in bts.items():
        p=base.portfolio_backtest(assets,use_capital_floor=False)
        curves[strategy]=p; rows.append({'portfolio':name,'strategy':strategy,'cost_one_way':cost,**base.metrics(p)})
    p=base.portfolio_backtest(bts['kartsell_v12_candidate'],use_capital_floor=True)
    curves['kartsell_v12_plus_capital_floor']=p
    rows.append({'portfolio':name,'strategy':'kartsell_v12_plus_capital_floor','cost_one_way':cost,**base.metrics(p)})
    return pd.DataFrame(rows),curves


def factor_panel(dfs: dict[str,pd.DataFrame], market_map: dict[str,str]) -> pd.DataFrame:
    frames=[]
    for s,df in dfs.items():
        x=df[['date','close']].copy().set_index('date')
        m=x['close'].resample('ME').last().dropna().to_frame('close')
        m['mom12_1']=m['close'].shift(1)/m['close'].shift(12)-1
        m['mom6_1']=m['close'].shift(1)/m['close'].shift(6)-1
        daily=df.set_index('date')['close'].pct_change()
        vol63=daily.rolling(63,min_periods=40).std()*math.sqrt(252)
        high252=df.set_index('date')['close'].rolling(252,min_periods=126).max()
        m['vol63']=vol63.resample('ME').last()
        m['drawdown252']=(df.set_index('date')['close']/high252-1).resample('ME').last()
        m['fwd12m']=m['close'].shift(-12)/m['close']-1
        m['symbol']=s; m['market']=market_map[s]
        frames.append(m.reset_index())
    return pd.concat(frames,ignore_index=True).dropna(subset=['fwd12m'])


def factor_summary(panel: pd.DataFrame) -> pd.DataFrame:
    rows=[]
    # Monthly cross-sectional terciles within region; no future data in factor formation.
    for fac,ascending in [('mom12_1',False),('mom6_1',False),('vol63',True),('drawdown252',False)]:
        p=panel.dropna(subset=[fac]).copy()
        p['rank_pct']=p.groupby(['market','date'])[fac].rank(pct=True,ascending=True)
        # For momentum/drawdown, high factor is preferred; for vol, low preferred.
        if fac=='vol63':
            top=p[p.rank_pct<=1/3]; bottom=p[p.rank_pct>=2/3]
        else:
            top=p[p.rank_pct>=2/3]; bottom=p[p.rank_pct<=1/3]
        for label,q in [('preferred_tercile',top),('opposite_tercile',bottom)]:
            r=q['fwd12m']
            rows.append({'factor':fac,'bucket':label,'observations':len(q),'mean_fwd12m':r.mean(),'median_fwd12m':r.median(),
                         'positive_rate':(r>0).mean(),'loss_gt_20_rate':(r<-.20).mean()})
        rows.append({'factor':fac,'bucket':'preferred_minus_opposite','observations':min(len(top),len(bottom)),
                     'mean_fwd12m':top.fwd12m.mean()-bottom.fwd12m.mean(),
                     'median_fwd12m':top.fwd12m.median()-bottom.fwd12m.median(),
                     'positive_rate':np.nan,'loss_gt_20_rate':np.nan})
    return pd.DataFrame(rows)


def core_tactical_spy(spy: pd.DataFrame):
    v12,_=base.v12_positions(spy,'broad_index_etf')
    out=[]
    for core in [.50,.65,.80]:
        p=v12[['date','position']].copy(); p['position']=core+(1-core)*p['position']
        bt=base.backtest(spy,p,cost=base.ONE_WAY_COST)
        out.append({'strategy':f'SPY_core_{int(core*100)}_tactical_{int((1-core)*100)}',**base.metrics(bt)})
    return pd.DataFrame(out)


def main():
    kr={s:load_kr(s,p) for s,p in KR_FILES.items()}
    pd.DataFrame([quality(s,KR_FILES[s],d) for s,d in kr.items()]).to_csv(OUT/'kr_data_quality.csv',index=False)

    kr_rows,kr_periods,kr_events,kr_bts=run_assets(kr,KR_CLASS,'KR',cost=.0010)
    kr_rows.to_csv(OUT/'kr_asset_metrics_10bps.csv',index=False)
    kr_periods.to_csv(OUT/'kr_period_metrics_10bps.csv',index=False)
    kr_events.to_csv(OUT/'kr_sell_event_stats.csv',index=False)
    kr_port,kr_curves=portfolio_from(kr_bts,'kr_three_equal_weight',cost=.0010)
    kr_port.to_csv(OUT/'kr_portfolio_metrics_10bps.csv',index=False)
    for n,c in kr_curves.items(): c.to_csv(OUT/f'kr_portfolio_{n}.csv',index=False)

    # Cost stress for sell-heavy strategies.
    stress=[]
    for cost in [.0007,.0015,.0030]:
        r,_,_,b=run_assets(kr,KR_CLASS,'KR',cost=cost)
        p,_=portfolio_from(b,f'kr_three_equal_weight_cost_{cost:.4f}',cost=cost)
        stress.append(p[p.strategy.isin(['kartsell_v11_original','kartsell_v12_candidate'])])
    pd.concat(stress,ignore_index=True).to_csv(OUT/'kr_cost_sensitivity.csv',index=False)

    us={s:base.load_symbol(s,p) for s,p in US_FILES.items() if s!='AAMRQ'}
    all_dfs={**us,**kr}
    market_map={**{s:'US' for s in us},**{s:'KR' for s in kr}}
    panel=factor_panel(all_dfs,market_map)
    panel.to_csv(OUT/'price_factor_panel_monthly.csv',index=False)
    factor_summary(panel).to_csv(OUT/'price_factor_summary.csv',index=False)
    core_tactical_spy(us['SPY']).to_csv(OUT/'spy_core_tactical_metrics.csv',index=False)

    print('KR DATA QUALITY')
    print(pd.read_csv(OUT/'kr_data_quality.csv').to_string(index=False))
    print('\nKR PORTFOLIO')
    print(kr_port[['strategy','cagr','mdd','calmar','turnover_annual','avg_exposure']].to_string(index=False))
    print('\nFACTOR SUMMARY')
    print(pd.read_csv(OUT/'price_factor_summary.csv').to_string(index=False))
    print('\nSPY CORE TACTICAL')
    print(pd.read_csv(OUT/'spy_core_tactical_metrics.csv')[['strategy','cagr','mdd','calmar','turnover_annual','avg_exposure']].to_string(index=False))

if __name__=='__main__': main()
