from pathlib import Path
from unittest.mock import patch
import random

from mbetterclient.core.games_thread import GamesThread
from mbetterclient.core.message_bus import Message, MessageBus, MessageType
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.database.models import (
    BetDetailModel,
    GameConfigModel,
    MatchModel,
    MatchOutcomeModel,
    PersistentRedistributionAdjustmentModel,
    ResultOptionModel,
    ExtractionAssociationModel,
)


def _build_games_thread(tmp_path: Path):
    db_path = tmp_path / "accounting_test.db"
    db_manager = DatabaseManager(str(db_path))
    assert db_manager.initialize()
    message_bus = MessageBus()
    games_thread = GamesThread("test_games_thread", message_bus, db_manager)
    return games_thread, db_manager


def _seed_match_and_bets(db_manager: DatabaseManager):
    session = db_manager.get_session()
    try:
        cap_config = session.query(GameConfigModel).filter_by(config_key='extraction_redistribution_cap').first()
        if cap_config is None:
            session.add(GameConfigModel(
                config_key='extraction_redistribution_cap',
                config_value='70.0',
                value_type='float',
                description='Test cap'
            ))
        else:
            cap_config.config_value = '70.0'
            cap_config.value_type = 'float'

        if session.query(ResultOptionModel).filter_by(result_name='WIN1').first() is None:
            session.add(ResultOptionModel(result_name='WIN1', description='Win 1', is_active=True, sort_order=1))

        for outcome_name in ('WIN1', 'KO1'):
            exists = session.query(ExtractionAssociationModel).filter_by(
                outcome_name=outcome_name,
                extraction_result='WIN1'
            ).first()
            if exists is None:
                session.add(ExtractionAssociationModel(outcome_name=outcome_name, extraction_result='WIN1'))

        match = MatchModel(
            match_number=1,
            fighter1_township='A',
            fighter2_township='B',
            venue_kampala_township='Kampala',
            filename='fixture.txt',
            file_sha1sum='fixture-sha1',
            fixture_id='fixture-1',
            active_status=True,
            status='bet'
        )
        session.add(match)
        session.flush()

        session.add_all([
            MatchOutcomeModel(match_id=match.id, column_name='UNDER', float_value=2.0),
            MatchOutcomeModel(match_id=match.id, column_name='OVER', float_value=2.5),
            MatchOutcomeModel(match_id=match.id, column_name='WIN1', float_value=3.0),
            MatchOutcomeModel(match_id=match.id, column_name='KO1', float_value=4.0),
        ])

        session.add_all([
            BetDetailModel(bet_id='bet-under', match_id=match.id, match_number=1, outcome='UNDER', amount=10.0, result='pending'),
            BetDetailModel(bet_id='bet-win1', match_id=match.id, match_number=1, outcome='WIN1', amount=20.0, result='pending'),
            BetDetailModel(bet_id='bet-ko1', match_id=match.id, match_number=1, outcome='KO1', amount=15.0, result='pending'),
        ])

        session.commit()
        return match.id, match.fixture_id
    finally:
        session.close()


def test_match_start_uses_settled_winnings_for_adjustment(tmp_path):
    games_thread, db_manager = _build_games_thread(tmp_path)
    match_id, fixture_id = _seed_match_and_bets(db_manager)

    message = Message(
        type=MessageType.MATCH_START,
        sender='test',
        recipient='test_games_thread',
        data={'fixture_id': fixture_id, 'match_id': match_id}
    )

    with patch('mbetterclient.core.games_thread.extract_match', return_value=('UNDER', 'WIN1', 0.0, 'WIN1, KO1')):
        with patch.object(games_thread, '_send_play_video_match', return_value=None):
            games_thread._handle_match_start(message)

    session = db_manager.get_session()
    try:
        global_record = session.query(PersistentRedistributionAdjustmentModel).filter_by(date='1970-01-01').one()
        winning_bets = session.query(BetDetailModel).filter_by(match_id=match_id, result='win').all()

        actual_redistributed = sum(float(bet.win_amount or 0.0) for bet in winning_bets)
        assert actual_redistributed == 140.0
        assert global_record.total_redistributed == actual_redistributed
        assert global_record.total_payin == 45.0
        assert global_record.accumulated_shortfall == (45.0 * 0.70) - actual_redistributed
    finally:
        session.close()
        db_manager.close()


def _seed_simulation_match(session, fixture_id: str, match_number: int, under_amount: float, over_amount: float, win1_amount: float, ko1_amount: float):
    match = MatchModel(
        match_number=match_number,
        fighter1_township=f'F{match_number}A',
        fighter2_township=f'F{match_number}B',
        venue_kampala_township='Kampala',
        filename=f'fixture-{match_number}.txt',
        file_sha1sum=f'fixture-sha1-{match_number}',
        fixture_id=fixture_id,
        active_status=True,
        status='bet'
    )
    session.add(match)
    session.flush()

    session.add_all([
        MatchOutcomeModel(match_id=match.id, column_name='UNDER', float_value=2.0),
        MatchOutcomeModel(match_id=match.id, column_name='OVER', float_value=2.0),
        MatchOutcomeModel(match_id=match.id, column_name='WIN1', float_value=2.0),
        MatchOutcomeModel(match_id=match.id, column_name='KO1', float_value=2.0),
    ])

    bet_rows = []
    for index, (outcome, amount) in enumerate((
        ('UNDER', under_amount),
        ('OVER', over_amount),
        ('WIN1', win1_amount),
        ('KO1', ko1_amount),
    ), start=1):
        if amount <= 0:
            continue
        bet_rows.append(BetDetailModel(
            bet_id=f'bet-{match_number}-{index}',
            match_id=match.id,
            match_number=match_number,
            outcome=outcome,
            amount=amount,
            result='pending'
        ))

    session.add_all(bet_rows)
    return match.id


def test_match_start_simulation_tracks_persisted_settled_totals(tmp_path):
    games_thread, db_manager = _build_games_thread(tmp_path)

    fixture_id = 'fixture-sim'
    rng = random.Random(7)
    rounds = 120

    total_payin = 0.0
    total_redistributed = 0.0

    session = db_manager.get_session()
    try:
        session.query(ExtractionAssociationModel).delete()
        session.query(ResultOptionModel).delete()
        session.add(ResultOptionModel(result_name='WIN1', description='Win 1', is_active=True, sort_order=1))
        session.add(ExtractionAssociationModel(outcome_name='WIN1', extraction_result='WIN1'))
        session.add(ExtractionAssociationModel(outcome_name='KO1', extraction_result='WIN1'))
        session.commit()
    finally:
        session.close()

    try:
        for match_number in range(1, rounds + 1):
            under_amount = rng.uniform(20.0, 60.0)
            over_amount = rng.uniform(20.0, 60.0)
            win1_amount = rng.uniform(20.0, 60.0)
            ko1_amount = rng.uniform(20.0, 60.0)

            session = db_manager.get_session()
            try:
                match_id = _seed_simulation_match(
                    session,
                    fixture_id,
                    match_number,
                    under_amount,
                    over_amount,
                    win1_amount,
                    ko1_amount,
                )
                session.commit()
            finally:
                session.close()

            message = Message(
                type=MessageType.MATCH_START,
                sender='test',
                recipient='test_games_thread',
                data={'fixture_id': fixture_id, 'match_id': match_id}
            )

            with patch.object(games_thread, '_send_play_video_match', return_value=None):
                games_thread._handle_match_start(message)

            session = db_manager.get_session()
            try:
                winning_bets = session.query(BetDetailModel).filter_by(match_id=match_id, result='win').all()
                match_payin = sum(float(bet.amount or 0.0) for bet in session.query(BetDetailModel).filter_by(match_id=match_id).all())
                match_redistributed = sum(float(bet.win_amount or 0.0) for bet in winning_bets)
                total_payin += match_payin
                total_redistributed += match_redistributed
            finally:
                session.close()

        session = db_manager.get_session()
        try:
            global_record = session.query(PersistentRedistributionAdjustmentModel).filter_by(date='1970-01-01').one()
            assert abs(global_record.total_payin - total_payin) < 1e-6
            assert abs(global_record.total_redistributed - total_redistributed) < 1e-6
            assert abs(
                global_record.accumulated_shortfall - ((total_payin * 0.70) - total_redistributed)
            ) < 1e-6
        finally:
            session.close()
    finally:
        db_manager.close()
