#!/usr/bin/env python3
"""
Comprehensive integration test: replay a synthetic game that exercises
every fixed bug, then verify final state + take screenshots.

Bugs exercised:
  #1  Trade property transfer
  #2  UI owner color (visual — screenshot)
  #5  Resign-to-bank clears properties
  #6  Resign-to-player transfers properties
  #7  Property owner renumbering after resign
  #8  spec flag cleared after rent
  #9  phase field always emitted
  #10 Resign/trade log timestamps
  #11 House counts from rent lines
  New: House buying via sub-parser
  New: Holdings display resync
  New: GOJF card command
  New: Mortgage/unmortgage tracking
"""

import sys
import os
import json
import unittest

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

TS = "2026-01-01 00:{:02d}:{:02d}"
_t = [0]

def ts():
    _t[0] += 1
    return TS.format(_t[0] // 60, _t[0] % 60)

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

def setup_game():
    """3-player game: alice, bob, charlie. charlie goes first."""
    p = MonopParser()
    feed(p, [
        "monop\tHow many players? ",
        "monop\tPlayer 1, say 'me' please.",
        "monop\talice (1) rolls 3",
        "monop\tPlayer 2, say 'me' please.",
        "monop\tbob (2) rolls 5",
        "monop\tPlayer 3, say 'me' please.",
        "monop\tcharlie (3) rolls 9",
        "monop\tcharlie (3) goes first",
        "monop\tcharlie (3) (cash $1500) on === GO ===",
        "monop\t-- Command: ",
    ])
    return p


class TestFullGameIntegration(unittest.TestCase):

    def test_full_game_flow(self):
        p = setup_game()
        g = p.game

        # === Phase field (Bug #9) ===
        state = p.get_state()
        self.assertIn("phase", state)
        self.assertEqual(state["phase"], "playing")

        # === charlie's turn: buy Mediterranean ===
        feed(p, [
            "charlie\t.",
            "monop\troll is 1, 0",
            "monop\tThat puts you on Mediterranean ave. (P)",
            "monop\tThat would cost $60",
            "monop\tDo you want to buy? ",
            "charlie\t.y",
            "monop\talice (1) (cash $1500) on === GO ===",
            "monop\t-- Command: ",
        ])
        self.assertEqual(g.property_owner.get(1), 3)  # charlie is #3

        # === alice's turn: buy Baltic ===
        feed(p, [
            "alice\t.",
            "monop\troll is 1, 2",
            "monop\tThat puts you on Baltic ave. (P)",
            "monop\tThat would cost $60",
            "monop\tDo you want to buy? ",
            "alice\t.y",
            "monop\tbob (2) (cash $1500) on === GO ===",
            "monop\t-- Command: ",
        ])
        self.assertEqual(g.property_owner.get(3), 1)  # alice is #1

        # === bob's turn: buy Reading RR ===
        feed(p, [
            "bob\t.",
            "monop\troll is 2, 3",
            "monop\tThat puts you on Reading RR",
            "monop\tThat would cost $200",
            "monop\tDo you want to buy? ",
            "bob\t.y",
            "monop\tcharlie (3) (cash $1440) on Mediterranean ave. (P)",
            "monop\t-- Command: ",
        ])
        self.assertEqual(g.property_owner.get(5), 2)  # bob is #2

        # === charlie buys Oriental, Vermont, Connecticut (full lightblue) ===
        feed(p, [
            "charlie\t.",
            "monop\troll is 3, 2",
            "monop\tThat puts you on Oriental ave. (L)",
            "monop\tThat would cost $100",
            "monop\tDo you want to buy? ",
            "charlie\t.y",
            "monop\talice (1) (cash $1440) on Baltic ave. (P)",
            "monop\t-- Command: ",
        ])
        g.property_owner[8] = 3   # Vermont (simulate)
        g.property_owner[9] = 3   # Connecticut (simulate)

        # === charlie buys houses on lightblue (Bug: house sub-parser) ===
        feed(p, [
            "charlie\t.buy",
            "monop\tOriental ave. (L) (0) Vermont ave. (L) (0) Connecticut ave. (L) (0) ",
            "monop\tHouses will cost $50",
            "monop\tHow many houses do you wish to buy for",
            "monop\tOriental ave. (L) (0): ",
            "charlie\t.2",
            "monop\tVermont ave. (L) (0): ",
            "charlie\t.2",
            "monop\tConnecticut ave. (L) (0): ",
            "charlie\t.2",
            "monop\tYou asked for 6 houses for $300",
            "monop\tIs that ok? ",
            "charlie\t.y",
            "monop\tcharlie (3) (cash $1040) on Oriental ave. (L)",
            "monop\t-- Command: ",
        ])
        self.assertEqual(g.property_houses.get(6), 2)   # Oriental 2 houses
        self.assertEqual(g.property_houses.get(8), 2)   # Vermont 2 houses
        self.assertEqual(g.property_houses.get(9), 2)   # Connecticut 2 houses

        # === alice lands on Oriental, pays rent with houses (Bug #11) ===
        feed(p, [
            "alice\t.",
            "monop\troll is 2, 1",
            "monop\tThat puts you on Oriental ave. (L)",
            "monop\tOwned by charlie",
            "monop\twith 2 houses, rent is 30",
            "monop\tbob (2) (cash $1300) on Reading RR",
            "monop\t-- Command: ",
        ])
        # Rent paid — house count confirmed from rent line (Bug #11)
        self.assertEqual(g.property_houses.get(6), 2)  # still 2 houses

        # === Trade: bob gives Reading RR to charlie for $300 (Bug #1) ===
        feed(p, [
            "monop\tPlayer bob (2) gives:",
            "monop\t Reading RR 2          200   1",
            "monop\tPlayer charlie (3) gives:",
            "monop\t     $300",
            "monop\tcharlie, is the trade ok? ",
            "charlie\t.y",
            "monop\tTrade is done!",
            "monop\tbob (2) (cash $1600) on Reading RR",
            "monop\t-- Command: ",
        ])
        # Reading RR should be charlie's now
        self.assertEqual(g.property_owner.get(5), 3, "Bug #1: Trade should transfer property")
        bob = g.get_player(name="bob")
        self.assertEqual(bob.money, 1600)

        # Verify trade log has timestamp (Bug #10)
        trade_logs = [e for e in g.log if "Trade completed" in e.get("text", "")]
        self.assertTrue(len(trade_logs) > 0)
        self.assertIn("timestamp", trade_logs[-1], "Bug #10: Trade log should have timestamp")

        # === Advance to alice's turn ===
        feed(p, [
            "bob\t.",
            "monop\troll is 1, 1",
            "monop\tThat puts you on Vermont ave. (L)",
            "monop\tOwned by charlie",
            "monop\twith 2 houses, rent is 30",
            "monop\tcharlie (3) (cash $800) on Oriental ave. (L)",
            "monop\t-- Command: ",
            # Bob rolled doubles, goes again
            "monop\tbob rolled doubles.  Goes again",
            "bob\t.",
            "monop\troll is 3, 2",
            "monop\tThat puts you on States ave. (V)",
            "monop\tThat is a safe place",
            "monop\talice (1) (cash $1140) on Oriental ave. (L)",
            "monop\t-- Command: ",
        ])

        # === alice mortgages Baltic (New: mortgage tracking) ===
        feed(p, [
            "alice\t.mor",
            "monop\tWhich property do you want to mortgage? ",
            "alice\t.baltic",
            "monop\tThat got you $30",
            "monop\talice (1) (cash $1170) on Oriental ave. (L)",
            "monop\t-- Command: ",
        ])
        self.assertTrue(g.property_mortgaged.get(3), "Mortgage flag should be set")

        # === alice unmortgages Baltic (New: unmortgage tracking) ===
        feed(p, [
            "alice\t.unm",
            "monop\tYour only mortaged property is Baltic ave. (P)",
            "monop\tDo you want to unmortgage it? ",
            "alice\t.y",
            "monop\tThat cost you $33",
            "monop\talice (1) (cash $1137) on Oriental ave. (L)",
            "monop\t-- Command: ",
        ])
        self.assertFalse(g.property_mortgaged.get(3, False), "Unmortgage should clear flag")

        # === charlie shows holdings (New: holdings resync) ===
        feed(p, [
            "monop\tcharlie's (3) holdings (Total worth: $3000):",
            "monop\t    Name      Own      Price Mg # Rent",
            "monop\t Oriental a 3 LtBlue  100   2   30",
            "monop\t Vermont av 3 LtBlue  100   2   30",
            "monop\t Connecticu 3 LtBlue  120   2   30",
            "monop\t Reading RR 3          200   1",
            "monop\t Mediterran 3          60        4",
            "monop\tcharlie (3) (cash $740) on Oriental ave. (L)",
            "monop\t-- Command: ",
        ])
        # Verify ownership resync
        self.assertEqual(g.property_owner.get(6), 3)   # Oriental
        self.assertEqual(g.property_owner.get(8), 3)   # Vermont
        self.assertEqual(g.property_owner.get(9), 3)   # Connecticut
        self.assertEqual(g.property_owner.get(5), 3)   # Reading RR
        self.assertEqual(g.property_owner.get(1), 3)   # Mediterranean
        # Verify house resync
        self.assertEqual(g.property_houses.get(6), 2)
        self.assertEqual(g.property_houses.get(8), 2)
        self.assertEqual(g.property_houses.get(9), 2)

        # === Advance: alice rolls, then bob's turn ===
        feed(p, [
            "alice\t.",
            "monop\troll is 4, 2",
            "monop\tThat puts you on Connecticut ave. (L)",
            "monop\tOwned by charlie",
            "monop\twith 2 houses, rent is 30",
            "monop\tbob (2) (cash $1570) on States ave. (V)",
            "monop\t-- Command: ",
        ])

        # === bob resigns to bank (Bug #5) ===
        feed(p, [
            "bob\t.resign",
            "monop\tWho do you wish to resign to? ",
            "bob\t.bank",
            "monop\tDo you really want to resign? ",
            "bob\t.y",
            "monop\tresigning to bank",
            "monop\tcharlie (1) (cash $770) on Oriental ave. (L)",
            "monop\t-- Command: ",
        ])
        # Bob should be bankrupt
        state = p.get_state()
        bankrupt = [pl for pl in state["players"] if pl.get("bankrupt")]
        active = [pl for pl in state["players"] if not pl.get("bankrupt")]
        self.assertEqual(len(bankrupt), 1)
        self.assertEqual(bankrupt[0]["name"], "bob")
        # Players renumbered (Bug #7)
        self.assertEqual(len(active), 2)
        # Property ownership renumbered correctly
        # charlie was 3, after bob (2) removed, charlie becomes 2
        charlie = g.get_player(name="charlie")
        self.assertIsNotNone(charlie)
        for sq_id in [1, 5, 6, 8, 9]:
            self.assertEqual(g.property_owner.get(sq_id), charlie.number,
                             f"Bug #7: Property {sq_id} should be renumbered to charlie's new number")

        # === GOJF card: charlie goes to jail, uses card (New) ===
        charlie = g.get_player(name="charlie")
        charlie.in_jail = True
        charlie.location = 40
        charlie.get_out_of_jail_free_cards = 1
        feed(p, [
            "charlie\t.card",
        ])
        self.assertFalse(charlie.in_jail, "GOJF card should free from jail")
        self.assertEqual(charlie.get_out_of_jail_free_cards, 0)
        self.assertEqual(charlie.location, 10)

        # === Advance to alice's turn ===
        feed(p, [
            "charlie\t.",
            "monop\troll is 3, 4",
            "monop\tThat puts you on Mediterranean ave. (P)",
            "monop\tYou own it.",
            "monop\talice (1) (cash $1107) on Connecticut ave. (L)",
            "monop\t-- Command: ",
        ])

        # === alice resigns to charlie (Bug #6) — game over ===
        alice = g.get_player(name="alice")
        g.property_owner[3] = alice.number  # give alice Baltic back for the test
        feed(p, [
            "monop\tYou would resign to charlie",
            "monop\tDo you really want to resign? ",
            "alice\t.y",
            "monop\tresigning to player",
            "monop\tTrade is done!",
            "monop\tThen charlie WINS!!!!!",
        ])

        state = p.get_state()
        self.assertEqual(state["phase"], "over", "Bug #9: phase should be 'over'")
        bankrupt = [pl for pl in state["players"] if pl.get("bankrupt")]
        self.assertEqual(len(bankrupt), 2)
        active = [pl for pl in state["players"] if not pl.get("bankrupt")]
        self.assertEqual(len(active), 1)
        self.assertEqual(active[0]["name"], "charlie")

        # Baltic should have transferred to charlie (Bug #6)
        charlie = g.get_player(name="charlie")
        self.assertEqual(g.property_owner.get(3), charlie.number,
                         "Bug #6: Resign-to-player should transfer properties")

        # Resign log should have timestamp (Bug #10)
        resign_logs = [e for e in g.log if "resigned" in e.get("text", "")]
        for entry in resign_logs:
            self.assertIn("timestamp", entry, "Bug #10: Resign log should have timestamp")

        # === Write state for screenshot ===
        return p


class TestStateOutput(unittest.TestCase):
    """Verify the full state JSON is well-formed for the UI."""

    def test_state_structure(self):
        p = setup_game()
        g = p.game
        # Give charlie some properties and houses
        g.property_owner[6] = 3
        g.property_owner[8] = 3
        g.property_owner[9] = 3
        g.property_houses[6] = 3
        g.property_houses[8] = 2
        g.property_houses[9] = 2
        g.property_owner[1] = 1
        g.property_mortgaged[1] = True

        state = p.get_state()

        # Verify structure
        self.assertIn("players", state)
        self.assertIn("squares", state)
        self.assertIn("log", state)
        self.assertIn("currentPlayer", state)
        self.assertIn("phase", state)

        # Verify squares have correct owner/house info
        oriental = next(sq for sq in state["squares"] if sq["id"] == 6)
        self.assertEqual(oriental["owner"], 3)
        self.assertEqual(oriental["houses"], 3)

        mediterranean = next(sq for sq in state["squares"] if sq["id"] == 1)
        self.assertEqual(mediterranean["owner"], 1)
        self.assertTrue(mediterranean["mortgaged"])

        # Verify all ownable squares have owner field
        for sq in state["squares"]:
            if sq["type"] in ("property", "railroad", "utility"):
                self.assertIn("owner", sq)
                self.assertIn("mortgaged", sq)


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