#!/usr/bin/env python3
"""Tests for player command tracking and holdings display parsing."""

import sys
import os
import unittest

sys.path.insert(0, os.path.dirname(__file__))
from monop_parser import MonopParser

TS = "2026-01-01 00:00:00"


def feed(parser, lines):
    for line in lines:
        parser.parse_line(f"{TS}\t{line}")


def setup_3player_game():
    """fbs goes first (turn order: fbs→merp→hiro)."""
    p = MonopParser()
    feed(p, [
        "monop\tHow many players? ",
        "monop\tPlayer 1, say 'me' please.",
        "monop\tmerp (1) rolls 5",
        "monop\tPlayer 2, say 'me' please.",
        "monop\thiro (2) rolls 3",
        "monop\tPlayer 3, say 'me' please.",
        "monop\tfbs (3) rolls 8",
        "monop\tfbs (3) goes first",
        "monop\tfbs (3) (cash $1500) on === GO ===",
        "monop\t-- Command: ",
    ])
    return p


def give_properties(game, player_num, sq_ids):
    for sq_id in sq_ids:
        game.property_owner[sq_id] = player_num


# =====================================================================
# GOJF card usage from player command
# =====================================================================
class TestGOJFCardCommand(unittest.TestCase):

    def test_card_command_exits_jail(self):
        """Player types .card while in jail with GOJF card."""
        p = setup_3player_game()
        g = p.game
        # Put fbs in jail with a GOJF card
        fbs = g.get_player(name="fbs")
        fbs.in_jail = True
        fbs.jail_turns = 1
        fbs.location = 40
        fbs.get_out_of_jail_free_cards = 1

        feed(p, ["fbs\t.card"])

        self.assertFalse(fbs.in_jail)
        self.assertEqual(fbs.location, 10)  # Just Visiting
        self.assertEqual(fbs.get_out_of_jail_free_cards, 0)
        self.assertEqual(fbs.jail_turns, 0)

    def test_card_command_no_gojf(self):
        """Player types .card but has no GOJF cards — nothing happens."""
        p = setup_3player_game()
        g = p.game
        fbs = g.get_player(name="fbs")
        fbs.in_jail = True
        fbs.location = 40
        fbs.get_out_of_jail_free_cards = 0

        feed(p, ["fbs\t.card"])

        # Should still be in jail
        self.assertTrue(fbs.in_jail)
        self.assertEqual(fbs.location, 40)

    def test_card_command_not_in_jail(self):
        """Player types .card but isn't in jail — nothing happens."""
        p = setup_3player_game()
        g = p.game
        fbs = g.get_player(name="fbs")
        fbs.get_out_of_jail_free_cards = 1

        feed(p, ["fbs\t.card"])

        # GOJF card should not be consumed
        self.assertEqual(fbs.get_out_of_jail_free_cards, 1)

    def test_card_log_entry(self):
        p = setup_3player_game()
        g = p.game
        fbs = g.get_player(name="fbs")
        fbs.in_jail = True
        fbs.location = 40
        fbs.get_out_of_jail_free_cards = 2

        feed(p, ["fbs\t.card"])

        log_texts = [e["text"] for e in g.log]
        self.assertTrue(any("Get Out of Jail" in t for t in log_texts))
        self.assertEqual(fbs.get_out_of_jail_free_cards, 1)


# =====================================================================
# Unmortgage tracking via command context
# =====================================================================
class TestUnmortgageTracking(unittest.TestCase):

    def test_unmortgage_clears_flag(self):
        """Player unmortgages a uniquely-identifiable property."""
        p = setup_3player_game()
        g = p.game
        # fbs owns Boardwalk (39), mortgaged
        give_properties(g, 3, [39])
        g.property_mortgaged[39] = True

        feed(p, [
            "fbs\t.unm",
            "monop\tYour only mortaged property is Boardwalk (D)",
            "monop\tDo you want to unmortgage it? ",
            "fbs\t.y",
            "monop\tThat cost you $220",  # 400/2 + 400/2/10 = 220
            "monop\tfbs (3) (cash $1280) on === GO ===",
            "monop\t-- Command: ",
        ])

        self.assertNotIn(39, g.property_mortgaged)

    def test_unmortgage_with_disambiguation(self):
        """Player unmortgages one of two same-cost properties."""
        p = setup_3player_game()
        g = p.game
        # fbs owns both Kentucky (21) and Indiana (23), both mortgaged
        # Both cost $220 → unmortgage cost $121 each
        give_properties(g, 3, [21, 23])
        g.property_mortgaged[21] = True
        g.property_mortgaged[23] = True

        feed(p, [
            "fbs\t.unm",
            "monop\tWhich property do you want to unmortgage? ",
            "fbs\t.indiana",  # user specifies Indiana
            "monop\tThat cost you $121",
            "monop\tfbs (3) (cash $1379) on === GO ===",
            "monop\t-- Command: ",
        ])

        # Indiana unmortgaged, Kentucky still mortgaged
        self.assertNotIn(23, g.property_mortgaged)
        self.assertTrue(g.property_mortgaged.get(21))


# =====================================================================
# Mortgage tracking
# =====================================================================
class TestMortgageTracking(unittest.TestCase):

    def test_mortgage_sets_flag(self):
        """Player mortgages a property — flag should be set."""
        p = setup_3player_game()
        g = p.game
        give_properties(g, 3, [39])  # Boardwalk

        feed(p, [
            "fbs\t.mor",
            "monop\tWhich property do you want to mortgage? ",
            "fbs\t.boardwalk",
            "monop\tThat got you $200",  # 400/2
            "monop\tfbs (3) (cash $1700) on === GO ===",
            "monop\t-- Command: ",
        ])

        self.assertTrue(g.property_mortgaged.get(39))


# =====================================================================
# Holdings display parsing
# =====================================================================
class TestHoldingsDisplay(unittest.TestCase):

    def test_holdings_syncs_properties(self):
        """Holdings display should resync property ownership."""
        p = setup_3player_game()
        g = p.game

        feed(p, [
            "monop\tmerp's (1) holdings (Total worth: $1806):",
            "monop\t    Name      Own      Price Mg # Rent",
            "monop\t Electric C 1          150   1",
            "monop\t New York a 1 Orange   200       16",
            "monop\t Kentucky a 1 RED      220   0   36",
            "monop\tmerp (1) (cash $496) on Community Chest ii",
            "monop\t-- Command: ",
        ])

        # Electric Co (12), New York ave (19), Kentucky ave (21) should be owned by merp (1)
        self.assertEqual(g.property_owner.get(12), 1)
        self.assertEqual(g.property_owner.get(19), 1)
        self.assertEqual(g.property_owner.get(21), 1)

    def test_holdings_syncs_mortgage_status(self):
        """Holdings display should set/clear mortgage flags."""
        p = setup_3player_game()
        g = p.game

        feed(p, [
            "monop\tmerp's (1) holdings (Total worth: $1806):",
            "monop\t    Name      Own      Price Mg # Rent",
            "monop\t Indiana av 1 RED      220 * 0   36",
            "monop\t Illinois a 1 RED      240   0   40",
            "monop\tmerp (1) (cash $496) on Community Chest ii",
            "monop\t-- Command: ",
        ])

        self.assertTrue(g.property_mortgaged.get(23))  # Indiana mortgaged
        self.assertNotIn(24, g.property_mortgaged)  # Illinois not mortgaged

    def test_holdings_syncs_house_counts(self):
        """Holdings display should update house counts."""
        p = setup_3player_game()
        g = p.game

        feed(p, [
            "monop\tfbs's (3) holdings (Total worth: $5000):",
            "monop\t    Name      Own      Price Mg # Rent",
            "monop\t Kentucky a 3 RED      220   3  180",
            "monop\t Indiana av 3 RED      220   3  180",
            "monop\t Illinois a 3 RED      240   4  220",
            "monop\tfbs (3) (cash $2000) on === GO ===",
            "monop\t-- Command: ",
        ])

        self.assertEqual(g.property_houses.get(21), 3)  # Kentucky 3 houses
        self.assertEqual(g.property_houses.get(23), 3)  # Indiana 3 houses
        self.assertEqual(g.property_houses.get(24), 4)  # Illinois 4 houses

    def test_holdings_clears_stale_houses(self):
        """If holdings shows 0 houses, clear any stale house count."""
        p = setup_3player_game()
        g = p.game
        g.property_houses[21] = 3  # stale: Kentucky had 3 houses

        feed(p, [
            "monop\tmerp's (1) holdings (Total worth: $1000):",
            "monop\t    Name      Own      Price Mg # Rent",
            "monop\t Kentucky a 1 RED      220   0   36",
            "monop\tmerp (1) (cash $500) on === GO ===",
            "monop\t-- Command: ",
        ])

        self.assertNotIn(21, g.property_houses)  # houses cleared

    def test_real_log_holdings_line_26(self):
        """Replay real holdings display from line 26 of test log."""
        p = MonopParser()
        with open("test_data/monop.log") as f:
            for i, line in enumerate(f, 1):
                p.parse_line(line.rstrip('\n'))
                if i >= 35:
                    break

        g = p.game
        self.assertIsNotNone(g)
        # merp (1) should own Electric Co (12) and others from the holdings dump
        self.assertEqual(g.property_owner.get(12), 1)  # Electric Co


# =====================================================================
# Real log: unmortgage at line 36-41
# =====================================================================
class TestRealLogUnmortgage(unittest.TestCase):

    def test_real_unmortgage(self):
        """Real log: merp unmortgages Indiana ave around line 36-41."""
        p = MonopParser()
        with open("test_data/monop.log") as f:
            for i, line in enumerate(f, 1):
                p.parse_line(line.rstrip('\n'))
                if i >= 42:
                    break

        g = p.game
        # Indiana ave (23) should NOT be mortgaged after unmortgage
        self.assertFalse(g.property_mortgaged.get(23, False),
                         "Indiana ave should be unmortgaged after .unm at line 36")


# =====================================================================
# House buying/selling integration (via HouseParser sub-parser)
# =====================================================================
class TestHouseIntegration(unittest.TestCase):

    def test_buy_houses_updates_state(self):
        """Full house buying flow updates property_houses in main parser."""
        p = setup_3player_game()
        g = p.game
        give_properties(g, 3, [1, 3])  # fbs owns purple monopoly

        feed(p, [
            "monop\tMediterranean ave. (P) (0) Baltic ave. (P) (0) ",
            "monop\tHouses will cost $50",
            "monop\tHow many houses do you wish to buy for",
            "monop\tMediterranean ave. (P) (0): ",
            "fbs\t.2",
            "monop\tBaltic ave. (P) (0): ",
            "fbs\t.2",
            "monop\tYou asked for 4 houses for $200",
            "monop\tIs that ok? ",
            "fbs\t.y",
            "monop\tfbs (3) (cash $1300) on === GO ===",
            "monop\t-- Command: ",
        ])

        self.assertEqual(g.property_houses.get(1), 2)  # Mediterranean
        self.assertEqual(g.property_houses.get(3), 2)  # Baltic

    def test_sell_houses_updates_state(self):
        """Full house selling flow updates property_houses."""
        p = setup_3player_game()
        g = p.game
        give_properties(g, 3, [21, 23, 24])  # fbs owns Red
        g.property_houses[21] = 2
        g.property_houses[23] = 2
        g.property_houses[24] = 3

        feed(p, [
            "monop\tHouses will get you $75 apiece",
            "monop\tKentucky ave. (R) (2) Indiana ave. (R) (2) Illinois ave. (R) (3) ",
            "monop\tHow many houses do you wish to sell from",
            "monop\tKentucky ave. (R) (2): ",
            "fbs\t.2",
            "monop\tIndiana ave. (R) (2): ",
            "fbs\t.2",
            "monop\tIllinois ave. (R) (3): ",
            "fbs\t.3",
            "monop\tYou asked to sell 7 houses for $525",
            "monop\tIs that ok? ",
            "fbs\t.y",
            "monop\tfbs (3) (cash $2025) on === GO ===",
            "monop\t-- Command: ",
        ])

        self.assertNotIn(21, g.property_houses)  # all gone
        self.assertNotIn(23, g.property_houses)
        self.assertNotIn(24, g.property_houses)

    def test_buy_rejected_no_change(self):
        """Rejected house purchase doesn't change state."""
        p = setup_3player_game()
        g = p.game
        give_properties(g, 3, [1, 3])

        feed(p, [
            "monop\tHouses will cost $50",
            "monop\tHow many houses do you wish to buy for",
            "monop\tMediterranean ave. (P) (0): ",
            "fbs\t.2",
            "monop\tBaltic ave. (P) (0): ",
            "fbs\t.2",
            "monop\tYou asked for 4 houses for $200",
            "monop\tIs that ok? ",
            "fbs\t.n",
        ])

        self.assertNotIn(1, g.property_houses)
        self.assertNotIn(3, g.property_houses)

    def test_real_log_house_buy_line_59(self):
        """Real log: merp buys houses on Red at line 59-73."""
        p = MonopParser()
        with open("test_data/monop.log") as f:
            for i, line in enumerate(f, 1):
                p.parse_line(line.rstrip('\n'))
                if i >= 73:
                    break

        g = p.game
        # Kentucky(21)=1, Indiana(23)=1, Illinois(24)=2
        self.assertEqual(g.property_houses.get(21), 1)
        self.assertEqual(g.property_houses.get(23), 1)
        self.assertEqual(g.property_houses.get(24), 2)


if __name__ == "__main__":
    unittest.main(verbosity=2)
