import os
import sys
from datetime import datetime
from types import SimpleNamespace

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from mbetterclient.database.migrations import Migration_069_RepairExtractionStatsFinancials
from mbetterclient.database.models import Base, BetDetailModel, ExtractionStatsModel, MatchModel


class StatRow:
    def __init__(self):
        self.match_id = 10
        self.total_bets = 99
        self.total_amount_collected = 9999.0
        self.total_redistributed = 8888.0
        self.under_bets = 9
        self.under_amount = 999.0
        self.over_bets = 8
        self.over_amount = 888.0
        self.actual_result = 'WIN1'
        self.extraction_result = 'WIN1'
        self.match_datetime = 'keep-me'
        self.result_breakdown = {'WIN1': {'bets': 2, 'amount': 50.0}}


class DetailRow:
    def __init__(self, outcome, amount, win_amount, result):
        self.outcome = outcome
        self.amount = amount
        self.win_amount = win_amount
        self.result = result


class SessionStub:
    def __init__(self, stats_rows, detail_rows_by_match_id):
        self.stats_rows = list(stats_rows)
        self.detail_rows_by_match_id = dict(detail_rows_by_match_id)
        self.committed = False
        self.rolled_back = False
        self.closed = False

    def query(self, model):
        model_name = getattr(model, '__name__', str(model))
        if model_name == 'ExtractionStatsModel':
            return QueryStub(self.stats_rows)
        if model_name == 'BetDetailModel':
            return DetailQueryStub(self.detail_rows_by_match_id)
        raise AssertionError(f'unexpected model: {model_name}')

    def commit(self):
        self.committed = True

    def rollback(self):
        self.rolled_back = True

    def close(self):
        self.closed = True


class QueryStub:
    def __init__(self, rows):
        self.rows = rows

    def all(self):
        return list(self.rows)


class DetailQueryStub:
    def __init__(self, detail_rows_by_match_id):
        self.detail_rows_by_match_id = detail_rows_by_match_id

    def filter_by(self, **kwargs):
        match_id = kwargs['match_id']
        return QueryStub(self.detail_rows_by_match_id.get(match_id, []))


class DummyDbManager:
    def __init__(self, session):
        self._session = session

    def get_session(self):
        return self._session


def test_repair_migration_recomputes_financial_fields_from_bet_details():
    migration = Migration_069_RepairExtractionStatsFinancials()
    stat = StatRow()
    details = [
        DetailRow('UNDER', 100.0, 0.0, 'lost'),
        DetailRow('WIN1', 50.0, 125.0, 'win'),
        DetailRow('OVER', 75.0, 0.0, 'cancelled'),
    ]

    repaired = migration._recompute_stat_row(stat, details)

    assert repaired.total_bets == 2
    assert repaired.total_amount_collected == 150.0
    assert repaired.total_redistributed == 125.0
    assert repaired.under_bets == 1
    assert repaired.under_amount == 100.0
    assert repaired.over_bets == 0
    assert repaired.over_amount == 0.0
    assert repaired.actual_result == 'WIN1'
    assert repaired.extraction_result == 'WIN1'
    assert repaired.match_datetime == 'keep-me'
    assert repaired.result_breakdown == {
        'UNDER': {'bets': 1, 'amount': 100.0},
        'WIN1': {'bets': 1, 'amount': 50.0},
    }


def test_repair_migration_ignores_unsupported_non_enum_result_values():
    migration = Migration_069_RepairExtractionStatsFinancials()
    stat = StatRow()
    details = [
        DetailRow('WIN1', 50.0, 125.0, 'won'),
    ]

    repaired = migration._recompute_stat_row(stat, details)

    assert repaired.total_bets == 1
    assert repaired.total_amount_collected == 50.0
    assert repaired.total_redistributed == 0.0
    assert repaired.result_breakdown == {
        'WIN1': {'bets': 1, 'amount': 50.0},
    }


def test_repair_migration_excludes_cancelled_bets_from_all_totals():
    migration = Migration_069_RepairExtractionStatsFinancials()
    stat = StatRow()
    details = [
        DetailRow('OVER', 40.0, 500.0, 'cancelled'),
    ]

    repaired = migration._recompute_stat_row(stat, details)

    assert repaired.total_bets == 0
    assert repaired.total_amount_collected == 0.0
    assert repaired.total_redistributed == 0.0
    assert repaired.over_bets == 0
    assert repaired.over_amount == 0.0
    assert repaired.result_breakdown == {}


def test_repair_migration_updates_rows_logs_duplicates_and_change_deltas(monkeypatch):
    migration = Migration_069_RepairExtractionStatsFinancials()
    stat_one = StatRow()
    stat_one.match_id = 10
    stat_two = StatRow()
    stat_two.match_id = 10
    stat_three = StatRow()
    stat_three.match_id = 11
    session = SessionStub(
        [stat_one, stat_two, stat_three],
        {
            10: [DetailRow('UNDER', 25.0, 0.0, 'lost')],
            11: [DetailRow('OVER', 40.0, 120.0, 'win')],
        },
    )

    models_module = SimpleNamespace(
        ExtractionStatsModel=type('ExtractionStatsModel', (), {}),
        BetDetailModel=type('BetDetailModel', (), {}),
    )
    monkeypatch.setitem(sys.modules, 'mbetterclient.database.models', models_module)

    info_logs = []
    warning_logs = []
    monkeypatch.setattr('mbetterclient.database.migrations.logger.info', lambda message: info_logs.append(message))
    monkeypatch.setattr('mbetterclient.database.migrations.logger.warning', lambda message: warning_logs.append(message))

    success = migration.up(DummyDbManager(session))

    assert success is True
    assert stat_one.total_bets == 1
    assert stat_one.total_amount_collected == 25.0
    assert stat_one.total_redistributed == 0.0
    assert stat_two.total_bets == 1
    assert stat_two.total_amount_collected == 25.0
    assert stat_two.total_redistributed == 0.0
    assert stat_three.total_bets == 1
    assert stat_three.total_amount_collected == 40.0
    assert stat_three.total_redistributed == 120.0
    assert stat_three.result_breakdown == {'OVER': {'bets': 1, 'amount': 40.0}}
    assert session.committed is True
    assert session.rolled_back is False
    assert session.closed is True
    assert any('Duplicate extraction stats rows detected for match_id 10' in message for message in warning_logs)
    assert any('Modified 3 extraction stats rows' in message for message in info_logs)
    assert any('match_id=10' in message and 'total_bets: 99 -> 1' in message for message in info_logs)
    assert any('match_id=10' in message and 'result_breakdown:' in message for message in info_logs)
    assert any('match_id=11' in message and 'total_redistributed: 8888.0 -> 120.0' in message for message in info_logs)


def test_repair_migration_runs_against_real_sqlalchemy_models(monkeypatch):
    engine = create_engine('sqlite:///:memory:')
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()

    match = MatchModel(
        id=77,
        match_number=1,
        fighter1_township='Town A',
        fighter2_township='Town B',
        venue_kampala_township='Kampala',
        filename='fixture.json',
        file_sha1sum='sha1-fixture',
        fixture_id='fixture-77',
        start_time=datetime(2026, 5, 1, 12, 0, 0),
        status='done',
        result='WIN1',
        winning_outcomes=['WIN1'],
        active_status=False,
    )
    stat = ExtractionStatsModel(
        match_id=77,
        fixture_id='fixture-77',
        match_datetime=datetime(2026, 5, 1, 12, 0, 0),
        total_bets=9,
        total_amount_collected=900.0,
        total_redistributed=800.0,
        actual_result='WIN1',
        result_breakdown='{"LEGACY":{"bets":9,"amount":900.0,"coefficient":2.0}}',
        under_bets=4,
        under_amount=400.0,
        over_bets=5,
        over_amount=500.0,
        extraction_result='WIN1',
        cap_applied=False,
    )
    detail_win = BetDetailModel(match_id=77, bet_id='bet-1', outcome='WIN1', amount=50.0, win_amount=125.0, result='win')
    detail_under = BetDetailModel(match_id=77, bet_id='bet-2', outcome='UNDER', amount=25.0, win_amount=0.0, result='lost')
    detail_cancelled = BetDetailModel(match_id=77, bet_id='bet-3', outcome='OVER', amount=100.0, win_amount=999.0, result='cancelled')
    session.add_all([match, stat, detail_win, detail_under, detail_cancelled])
    session.commit()

    info_logs = []
    warning_logs = []
    monkeypatch.setattr('mbetterclient.database.migrations.logger.info', lambda message: info_logs.append(message))
    monkeypatch.setattr('mbetterclient.database.migrations.logger.warning', lambda message: warning_logs.append(message))

    migration = Migration_069_RepairExtractionStatsFinancials()
    success = migration.up(DummyDbManager(session))
    session.expire_all()
    repaired = session.query(ExtractionStatsModel).filter_by(match_id=77).one()

    assert success is True
    assert repaired.total_bets == 2
    assert repaired.total_amount_collected == 75.0
    assert repaired.total_redistributed == 125.0
    assert repaired.under_bets == 1
    assert repaired.under_amount == 25.0
    assert repaired.over_bets == 0
    assert repaired.over_amount == 0.0
    assert repaired.get_result_breakdown() == {
        'UNDER': {'bets': 1, 'amount': 25.0},
        'WIN1': {'bets': 1, 'amount': 50.0},
    }
    assert any('Modified 1 extraction stats rows out of 1 processed' in message for message in info_logs)
    assert any('result_breakdown:' in message for message in info_logs)
    assert not warning_logs

    session.close()
    engine.dispose()
