"""Custom cumulative scoring; no broker connection or live clearance mechanism."""
from data_paths import default_db_path
import argparse
import json
import sqlite3
from datetime import date, datetime, timezone
from decimal import Decimal
from pathlib import Path

PROFILES = {
    'BASELINE_85_V1': {'dd': Decimal(5), 'breaker': Decimal(30), 'pnl': Decimal('.05'), 'cap': Decimal(15), 'threshold': Decimal(85), 'sessions': 90},
    'ELITE_97_V3': {'dd': Decimal(8), 'breaker': Decimal(40), 'pnl': Decimal('.02'), 'cap': Decimal(5), 'threshold': Decimal(97), 'sessions': 100}
}
PROVENANCE = {'SYNTHETIC_FIXTURE', 'MANUAL_UNVERIFIED', 'RECORDED_SESSION_LEDGER'}


def numeric(value):
    if type(value) not in (str,int,Decimal):
        raise ValueError('Boolean is not a numeric metric')
    try:
        result = Decimal(str(value))
    except ArithmeticError:
        raise ValueError('Invalid numeric metric') from None
    if not result.is_finite():
        raise ValueError('Metrics must be finite')
    return result


def session_date(value):
    parsed = date.fromisoformat(value)
    if parsed.isoformat() != value:
        raise ValueError('Session date must use YYYY-MM-DD')
    return value


def canonical_number(value):
    return format(numeric(value).normalize(), 'f')


def calculate_score(profile, pnl, max_dd_pct, breaker_tripped):
    cfg = PROFILES[profile]
    pnl, dd = numeric(pnl), numeric(max_dd_pct)
    if not 0 <= dd <= 100 or type(breaker_tripped) is not bool:
        raise ValueError('Drawdown must be 0..100 percent and breaker must be boolean')
    modifier = min(max(pnl * cfg['pnl'], -cfg['cap']), cfg['cap'])
    return min(Decimal(100), max(Decimal(0), Decimal(100) - dd * cfg['dd']
        - (cfg['breaker'] if breaker_tripped else Decimal(0)) + modifier))


def open_existing(path, read_only=False):
    p = Path(path).resolve()
    if not p.is_file():
        raise ValueError('Existing database required; no new ledger created')
    c = sqlite3.connect(p.as_uri() + ('?mode=ro' if read_only else '?mode=rw'), uri=True, timeout=3)
    c.row_factory = sqlite3.Row
    if read_only:
        c.execute('PRAGMA query_only=ON')
    return c


def initialize_score_ledger(db_filepath):
    c = open_existing(db_filepath)
    try:
        # An existing legacy date-only table needs an explicit migration, not silent overwriting.
        exists = c.execute("SELECT 1 FROM sqlite_master WHERE name='daily_performance_scores'").fetchone()
        if exists:
            cols = {r[1] for r in c.execute('PRAGMA table_info(daily_performance_scores)')}
            if not {'score_profile', 'execution_tag', 'input_provenance', 'session_halted', 'source_reference'} <= cols:
                raise ValueError('Legacy score schema preserved; explicit migration required')
        c.executescript((Path(__file__).resolve().parent / 'init_score_ledger.sql').read_text())
        c.commit()
    finally:
        c.close()


class CumulativeScoreGatekeeper:
    def __init__(self, db_filepath=str(default_db_path('foresixa_multiera_fixture.db')), *, profile='BASELINE_85_V1', execution_tag='ALL', read_only=False, _connection=None):
        if profile not in PROFILES or not isinstance(execution_tag, str) or not execution_tag.strip():
            raise ValueError('Explicit profile and worker scope required')
        self.db_filepath, self.profile, self.execution_tag = str(db_filepath), profile, execution_tag
        self._owns_connection = _connection is None
        self.conn = open_existing(db_filepath, read_only) if _connection is None else _connection

    def process_and_log_daily_score(self, date_str, pnl, max_dd_pct, breaker_tripped, *,
            input_provenance='MANUAL_UNVERIFIED', source_reference='caller-supplied metrics; unverified', session_halted=False):
        session_date(date_str)
        if input_provenance not in PROVENANCE or not isinstance(source_reference, str) or not source_reference.strip() or type(session_halted) is not bool:
            raise ValueError('Valid provenance, source reference and halt boolean required')
        pnl, dd = numeric(pnl), numeric(max_dd_pct)
        score = calculate_score(self.profile, pnl, dd, breaker_tripped)
        values = (self.profile, self.execution_tag, date_str, canonical_number(pnl), canonical_number(dd),
            int(breaker_tripped), int(session_halted), canonical_number(score), input_provenance, source_reference)
        owns_transaction = not self.conn.in_transaction
        try:
            if owns_transaction:
                self.conn.execute('BEGIN IMMEDIATE')
            existing = self.conn.execute('''SELECT score_profile, execution_tag, historical_date_str,
                net_pnl_realized, max_drawdown_observed_pct, circuit_breaker_triggered,
                session_halted, calculated_session_score, input_provenance, source_reference
                FROM daily_performance_scores WHERE score_profile=? AND execution_tag=? AND historical_date_str=?''', values[:3]).fetchone()
            if existing:
                if tuple(existing) != values:
                    raise ValueError('Conflicting session score preserved; review source metrics before correction')
                if owns_transaction:
                    self.conn.commit()
                return {'status': 'ALREADY_RECORDED', 'score': str(score)}
            self.conn.execute('''INSERT INTO daily_performance_scores
                (score_profile, execution_tag, historical_date_str, net_pnl_realized,
                 max_drawdown_observed_pct, circuit_breaker_triggered, session_halted,
                 calculated_session_score, input_provenance, source_reference, logged_at)
                VALUES (?,?,?,?,?,?,?,?,?,?,?)''', values + (datetime.now(timezone.utc).isoformat(),))
            if owns_transaction:
                self.conn.commit()
            return {'status': 'SCORE_RECORDED', 'score': str(score)}
        except Exception:
            if owns_transaction:
                self.conn.rollback()
            raise

    def execute_cumulative_gatekeeper_audit(self, mandatory_passing_score=None, mandatory_sessions=None, *, _rolling_window=None):
        cfg = PROFILES[self.profile]
        threshold = cfg['threshold'] if mandatory_passing_score is None else numeric(mandatory_passing_score)
        sessions_required = cfg['sessions'] if mandatory_sessions is None else mandatory_sessions
        # A caller cannot relax the named policy by changing arguments.
        if threshold != cfg['threshold'] or type(sessions_required) is not int or sessions_required != cfg['sessions']:
            raise ValueError('Named profile thresholds cannot be overridden')
        if _rolling_window is not None and (self.profile != 'ELITE_97_V3' or type(_rolling_window) is not int or _rolling_window != 100):
            raise ValueError('Rolling elite policy requires exactly 100 sessions')
        report = {'DECISION_TOKEN': 'SCORE_GATE_FAILED_NO_LIVE_CLEARANCE', 'SCORE_PROFILE': self.profile,
            'EXECUTION_TAG': self.execution_tag, 'PASSING_SCORE_REQUIRED': str(threshold),
            'SESSIONS_REQUIRED': sessions_required, 'TOTAL_LOGGED_SESSIONS': 0,
            'ELIGIBLE_RECORDED_SESSIONS': 0, 'CUMULATIVE_PORTFOLIO_GRADE': None,
            'ELIGIBLE_CUMULATIVE_GRADE': None, 'TOTAL_CIRCUIT_BREAKER_EVENTS': 0,
            'CONSECUTIVE_SCHEDULED_SESSIONS_COMPLETE': False,
            'HARD_RISK_VETO': False, 'LIVE_CLEARANCE_GRANTED': False,
            'LIVE_EXECUTION_ENABLED': False, 'HALT_REQUESTED': True,
            'EXTERNAL_PROCESS_SHUTDOWN_PERFORMED': False,
            'AUDIT_MODE': 'ROLLING_100' if _rolling_window else 'CUMULATIVE',
            'EVALUATION_SUMMARY': 'Custom grade and recorded-session counts do not establish trading readiness.'}
        owns_snapshot = not self.conn.in_transaction
        try:
            if owns_snapshot:
                self.conn.execute('BEGIN')
            total_rows = self.conn.execute('SELECT COUNT(*) FROM daily_performance_scores WHERE score_profile=? AND execution_tag=?', (self.profile, self.execution_tag)).fetchone()[0]
            if _rolling_window:
                rows = self.conn.execute('SELECT * FROM daily_performance_scores WHERE score_profile=? AND execution_tag=? ORDER BY historical_date_str DESC LIMIT ?', (self.profile, self.execution_tag, _rolling_window)).fetchall()
                rows = list(reversed(rows))
            else:
                rows = self.conn.execute('SELECT * FROM daily_performance_scores WHERE score_profile=? AND execution_tag=? ORDER BY historical_date_str', (self.profile, self.execution_tag)).fetchall()
            scores, eligible, breakers, hard_veto = [], [], 0, False
            for r in rows:
                session_date(r['historical_date_str'])
                if r['historical_date_str']>date.today().isoformat():raise ValueError('Future session rejected')
                if r['circuit_breaker_triggered'] not in (0, 1) or r['session_halted'] not in (0, 1) or r['input_provenance'] not in PROVENANCE:
                    raise ValueError('Invalid stored score metadata')
                recalculated = calculate_score(self.profile, r['net_pnl_realized'], r['max_drawdown_observed_pct'], bool(r['circuit_breaker_triggered']))
                if numeric(r['calculated_session_score']) != recalculated:
                    raise ValueError('Stored score differs from its metrics')
                if r['circuit_breaker_triggered'] or r['session_halted'] or numeric(r['max_drawdown_observed_pct'])>=3:
                    hard_veto=True
                scores.append(recalculated)
                breakers += r['circuit_breaker_triggered']
                if r['input_provenance'] == 'RECORDED_SESSION_LEDGER':
                    metric=self.conn.execute('SELECT * FROM daily_session_metrics WHERE execution_tag=? AND historical_date_str=?',(self.execution_tag,r['historical_date_str'])).fetchone()
                    if (not metric or metric['session_complete']!=1 or metric['equity_observation_count']<2 or
                        metric['input_provenance']!='RECORDED_SESSION_LEDGER' or not metric['source_reference'].strip() or
                        numeric(metric['net_pnl_realized'])!=numeric(r['net_pnl_realized']) or
                        numeric(metric['max_drawdown_observed_pct'])!=numeric(r['max_drawdown_observed_pct']) or
                        metric['circuit_breaker_triggered']!=r['circuit_breaker_triggered'] or metric['session_halted']!=r['session_halted']):
                        continue
                    eligible.append((r['historical_date_str'], recalculated))
                    if numeric(r['max_drawdown_observed_pct']) >= 3 or r['session_halted']:
                        hard_veto = True
            avg = sum(scores, Decimal(0)) / len(scores) if scores else None
            eligible_avg = sum((s for _, s in eligible), Decimal(0)) / len(eligible) if eligible else None
            schedule = self.conn.execute('SELECT session_ordinal, historical_date_str FROM expected_scoring_sessions WHERE execution_tag=? ORDER BY session_ordinal', (self.execution_tag,)).fetchall()
            complete = False
            if schedule:
                for r in schedule: session_date(r['historical_date_str'])
                ordinals = [r['session_ordinal'] for r in schedule]
                dates = [r['historical_date_str'] for r in schedule]
                if ordinals != list(range(1,len(schedule)+1)) or dates != sorted(set(dates)):
                    raise ValueError('Session calendar must be ordered and contiguous by ordinal')
                scored_dates = {d for d, _ in eligible}
                # The latest required window must be complete; sparse dates across eras cannot qualify.
                complete = len(dates) >= sessions_required and set(dates[-sessions_required:]) <= scored_dates
            passed = eligible_avg is not None and eligible_avg >= threshold and len(eligible) >= sessions_required and complete and not hard_veto
            report.update(TOTAL_LOGGED_SESSIONS=total_rows, CURRENT_WINDOW_DATA_SIZE=len(rows), ELIGIBLE_RECORDED_SESSIONS=len(eligible),
                CUMULATIVE_PORTFOLIO_GRADE=str(avg) if avg is not None else None,
                ELIGIBLE_CUMULATIVE_GRADE=str(eligible_avg) if eligible_avg is not None else None,
                TOTAL_CIRCUIT_BREAKER_EVENTS=breakers,
                CONSECUTIVE_SCHEDULED_SESSIONS_COMPLETE=complete, HARD_RISK_VETO=hard_veto,
                HALT_REQUESTED=not passed,
                DECISION_TOKEN='RESEARCH_SCORE_THRESHOLD_MET_NO_LIVE_CLEARANCE' if passed else 'SCORE_GATE_FAILED_NO_LIVE_CLEARANCE')
            if _rolling_window:
                report.update(WINDOW_SIZE=_rolling_window, LAST_100_CUMULATIVE_GRADE=report['CUMULATIVE_PORTFOLIO_GRADE'],
                    BREAKER_EVENTS_IN_ACTIVE_WINDOW=breakers,
                    WINDOW_START_DATE=rows[0]['historical_date_str'] if rows else None,
                    WINDOW_END_DATE=rows[-1]['historical_date_str'] if rows else None)
            return report
        except (sqlite3.Error, ValueError, ArithmeticError, IndexError, TypeError, KeyError, AttributeError):
            report.update(DECISION_TOKEN='SCORE_DATA_NOT_VERIFIED', EVALUATION_SUMMARY='Missing, incompatible or invalid scoring evidence; clearance withheld.')
            return report
        finally:
            if owns_snapshot:
                self.conn.rollback()

    def close(self):
        if self._owns_connection:
            self.conn.close()


def run_cli(profile='BASELINE_85_V1', rolling=False):
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--db-path',required=True)
    parser.add_argument('--execution-tag',required=True)
    args=parser.parse_args();auditor=None
    try:
        auditor=CumulativeScoreGatekeeper(args.db_path,profile=profile,execution_tag=args.execution_tag,read_only=True)
        report=auditor.execute_cumulative_gatekeeper_audit(_rolling_window=100 if rolling else None)
    except (ValueError,sqlite3.Error,ArithmeticError):
        report={'DECISION_TOKEN':'SCORE_DATA_NOT_VERIFIED','LIVE_CLEARANCE_GRANTED':False,'LIVE_EXECUTION_ENABLED':False,'HALT_REQUESTED':True}
    finally:
        if auditor:auditor.close()
    print(json.dumps(report,indent=2,allow_nan=False))
    return 0 if report['DECISION_TOKEN']=='RESEARCH_SCORE_THRESHOLD_MET_NO_LIVE_CLEARANCE' else 1

if __name__=='__main__':raise SystemExit(run_cli())
