import json

from django.test import SimpleTestCase

from apps.epic.models import TarotCard


def _card(arcana, number, suit='', icon=''):
    c = TarotCard()
    c.arcana = arcana
    c.number = number
    c.suit = suit
    c.icon = icon
    return c


class TarotCardCornerRankTest(SimpleTestCase):
    """TarotCard.corner_rank — alphanumeric display labels."""

    def test_major_arcana_0_gives_0(self):
        self.assertEqual(_card('MAJOR', 0).corner_rank, '0')

    def test_major_arcana_1_gives_roman_I(self):
        self.assertEqual(_card('MAJOR', 1).corner_rank, 'I')

    def test_major_arcana_2_gives_roman_II(self):
        self.assertEqual(_card('MAJOR', 2).corner_rank, 'II')

    def test_non_major_pip_1_gives_A(self):
        """Ace — pip card number 1 should show 'A', not '1'."""
        self.assertEqual(_card('MIDDLE', 1, 'BRANDS').corner_rank, 'A')

    def test_non_major_pip_2_gives_2(self):
        self.assertEqual(_card('MIDDLE', 2, 'BRANDS').corner_rank, '2')

    def test_non_major_pip_10_gives_10(self):
        self.assertEqual(_card('MIDDLE', 10, 'CROWNS').corner_rank, '10')

    def test_court_maid_gives_M(self):
        self.assertEqual(_card('MIDDLE', 11, 'GRAILS').corner_rank, 'M')

    def test_court_jack_gives_J(self):
        self.assertEqual(_card('MIDDLE', 12, 'BLADES').corner_rank, 'J')

    def test_court_queen_gives_Q(self):
        self.assertEqual(_card('MIDDLE', 13, 'BRANDS').corner_rank, 'Q')

    def test_court_king_gives_K(self):
        self.assertEqual(_card('MIDDLE', 14, 'CROWNS').corner_rank, 'K')

    def test_major_arcana_fiorentine_additive_overrides(self):
        """The 6 trumps the Fiorentine deck prints in additive form (NOT a
        universal pattern shift — e.g. trump 9 still prints IX, trump 14
        still prints XIV, per the actual deck art). Locked-in list."""
        self.assertEqual(_card('MAJOR', 4).corner_rank,  'IIII')
        self.assertEqual(_card('MAJOR', 19).corner_rank, 'XVIIII')
        self.assertEqual(_card('MAJOR', 24).corner_rank, 'XXIIII')
        self.assertEqual(_card('MAJOR', 29).corner_rank, 'XXVIIII')
        self.assertEqual(_card('MAJOR', 34).corner_rank, 'XXXIIII')
        self.assertEqual(_card('MAJOR', 39).corner_rank, 'XXXVIIII')

    def test_major_arcana_non_overridden_ranks_stay_subtractive(self):
        """Trumps NOT in the additive-override list keep standard subtractive
        Roman numerals. Regression guard against accidental pattern-shift."""
        self.assertEqual(_card('MAJOR', 9).corner_rank,  'IX')
        self.assertEqual(_card('MAJOR', 14).corner_rank, 'XIV')
        self.assertEqual(_card('MAJOR', 40).corner_rank, 'XL')
        self.assertEqual(_card('MAJOR', 44).corner_rank, 'XLIV')
        self.assertEqual(_card('MAJOR', 49).corner_rank, 'XLIX')


class TarotCardSuitIconTest(SimpleTestCase):
    """TarotCard.suit_icon — icon class resolution."""

    def test_major_with_icon_returns_icon(self):
        self.assertEqual(_card('MAJOR', 0, icon='fa-hat-cowboy-side').suit_icon, 'fa-hat-cowboy-side')

    def test_major_without_icon_defaults_to_hand_dots(self):
        # Sprint A.7.5 — trump fallback for the stat-block chip + text-mode
        # corners. Per-card `icon` still wins (covered by the test above).
        self.assertEqual(_card('MAJOR', 5).suit_icon, 'fa-hand-dots')

    def test_brands_returns_wand_sparkles(self):
        self.assertEqual(_card('MIDDLE', 11, 'BRANDS').suit_icon, 'fa-wand-sparkles')

    def test_grails_returns_trophy(self):
        self.assertEqual(_card('MIDDLE', 11, 'GRAILS').suit_icon, 'fa-trophy')

    def test_blades_returns_gun(self):
        self.assertEqual(_card('MIDDLE', 11, 'BLADES').suit_icon, 'fa-gun')

    def test_crowns_returns_crown(self):
        self.assertEqual(_card('MIDDLE', 11, 'CROWNS').suit_icon, 'fa-crown')

    def test_icon_override_takes_priority_over_suit(self):
        self.assertEqual(_card('MIDDLE', 11, 'CROWNS', icon='fa-star').suit_icon, 'fa-star')


class TarotCardEmanationForTest(SimpleTestCase):
    """TarotCard.emanation_for — polarity-split upright title (cards 48-49)."""

    def test_returns_levity_emanation_when_polarity_levity(self):
        c = TarotCard()
        c.name = 'Group 11: The Awakened'
        c.levity_emanation = 'The Effulgent Mould of Man'
        c.gravity_emanation = 'The Tellurian Mould of Man'
        self.assertEqual(c.emanation_for('levity'), 'The Effulgent Mould of Man')

    def test_returns_gravity_emanation_when_polarity_gravity(self):
        c = TarotCard()
        c.name = 'Group 11: The Awakened'
        c.levity_emanation = 'The Effulgent Mould of Man'
        c.gravity_emanation = 'The Tellurian Mould of Man'
        self.assertEqual(c.emanation_for('gravity'), 'The Tellurian Mould of Man')

    def test_falls_back_to_name_title_when_no_levity_split(self):
        c = TarotCard()
        c.name = 'Group 3: The Nomad'
        self.assertEqual(c.emanation_for('levity'), 'The Nomad')

    def test_falls_back_to_name_title_when_no_gravity_split(self):
        c = TarotCard()
        c.name = 'Group 3: The Nomad'
        self.assertEqual(c.emanation_for('gravity'), 'The Nomad')


class TarotCardReversalForTest(SimpleTestCase):
    """TarotCard.reversal_for — polarity-split reversal title (card 48)."""

    def test_returns_levity_reversal_when_polarity_levity(self):
        c = TarotCard()
        c.name = 'Group 11: The Awakened'
        c.levity_reversal = 'The Reflected Mould of Man'
        c.gravity_reversal = 'The Obscured Mould of Man'
        self.assertEqual(c.reversal_for('levity'), 'The Reflected Mould of Man')

    def test_returns_gravity_reversal_when_polarity_gravity(self):
        c = TarotCard()
        c.name = 'Group 11: The Awakened'
        c.levity_reversal = 'The Reflected Mould of Man'
        c.gravity_reversal = 'The Obscured Mould of Man'
        self.assertEqual(c.reversal_for('gravity'), 'The Obscured Mould of Man')

    def test_falls_back_to_reversal_qualifier_when_no_polarity_split(self):
        c = TarotCard()
        c.name = 'Jack of Brands'
        c.reversal_qualifier = 'Fickle'
        # No polarity-split override → reversal_qualifier wins
        self.assertEqual(c.reversal_for('levity'), 'Fickle')
        self.assertEqual(c.reversal_for('gravity'), 'Fickle')

    def test_falls_back_to_emanation_when_no_reversal_qualifier(self):
        """Blank reversal_qualifier → reversal_for falls through emanation_for."""
        c = TarotCard()
        c.name = 'Group 3: The Nomad'
        # reversal_qualifier blank, no polarity-split reversal → emanation fallback
        self.assertEqual(c.reversal_for('levity'), 'The Nomad')


class TarotCardAppletFaceTest(SimpleTestCase):
    """TarotCard.applet_face — face rendering payload for the My Sign / My
    Sea applets. Mirrors `populateCard` in `stage-card.js:135-144`."""

    def test_major_with_qualifier_renders_title_with_comma_qualifier_below(self):
        # Trump 9 — Major Arcana w. lev/grav qualifiers ("Sublimating" /
        # "Sedimentary"). The applet face should match the page's stage
        # card layout: "Title," on top, qualifier below.
        c = TarotCard()
        c.name = "Erasing Personal History"
        c.arcana = TarotCard.MAJOR
        c.levity_qualifier = "Sublimating"
        c.gravity_qualifier = "Sedimentary"
        face = c.applet_face("levity", reversed=False)
        self.assertEqual(face["title"], "Erasing Personal History,")
        self.assertEqual(face["qualifier"], "Sublimating")
        self.assertFalse(face["qualifier_first"])

    def test_major_with_qualifier_gravity_polarity(self):
        c = TarotCard()
        c.name = "Erasing Personal History"
        c.arcana = TarotCard.MAJOR
        c.levity_qualifier = "Sublimating"
        c.gravity_qualifier = "Sedimentary"
        face = c.applet_face("gravity", reversed=False)
        self.assertEqual(face["title"], "Erasing Personal History,")
        self.assertEqual(face["qualifier"], "Sedimentary")
        self.assertFalse(face["qualifier_first"])

    def test_non_major_renders_qualifier_above_title(self):
        # Queen of Crowns — Middle court. Qualifier ABOVE title (no comma).
        c = TarotCard()
        c.name = "Queen of Crowns"
        c.arcana = TarotCard.MIDDLE
        c.levity_qualifier = "Elevated"
        c.gravity_qualifier = "Graven"
        face = c.applet_face("levity", reversed=False)
        self.assertEqual(face["title"], "Queen of Crowns")
        self.assertEqual(face["qualifier"], "Elevated")
        self.assertTrue(face["qualifier_first"])

    def test_polarity_split_emanation_single_line_no_qualifier(self):
        # Card 48-49 — polarity-split title, qualifier blank.
        c = TarotCard()
        c.name = "Group 11: The Awakened"
        c.arcana = TarotCard.MAJOR
        c.levity_emanation = "The Effulgent Mould of Man"
        c.gravity_emanation = "The Tellurian Mould of Man"
        face = c.applet_face("levity", reversed=False)
        self.assertEqual(face["title"], "The Effulgent Mould of Man")
        self.assertEqual(face["qualifier"], "")
        self.assertFalse(face["qualifier_first"])

    def test_reversed_polarity_split_uses_reversal_title(self):
        c = TarotCard()
        c.name = "Group 11: The Awakened"
        c.arcana = TarotCard.MAJOR
        c.levity_reversal = "The Reflected Mould of Man"
        c.gravity_reversal = "The Obscured Mould of Man"
        face = c.applet_face("levity", reversed=True)
        self.assertEqual(face["title"], "The Reflected Mould of Man")
        self.assertEqual(face["qualifier"], "")

    def test_non_major_reversed_falls_back_to_polarity_qualifier(self):
        # Two of Crowns reversed (MINOR), no explicit reversal_qualifier
        # → uses polarity qualifier as fallback. Qualifier above title.
        c = TarotCard()
        c.name = "Two of Crowns"
        c.arcana = TarotCard.MINOR
        c.levity_qualifier = "Relieving"
        c.gravity_qualifier = "Grieving"
        face = c.applet_face("gravity", reversed=True)
        self.assertEqual(face["title"], "Two of Crowns")
        self.assertEqual(face["qualifier"], "Grieving")
        self.assertTrue(face["qualifier_first"])

    def test_pattern_b_major_reversal_swaps_name_keeps_polarity_qualifier(self):
        """User-spec 2026-05-23: cards 2-5, 10-15, 22-35, 41 — Major Arcana
        w. BOTH polarity qualifiers AND a `reversal_qualifier` should treat
        `reversal_qualifier` as a NAME SWAP for the reversal face. The
        polarity qualifier persists across both faces. Example: card 34
        Animal Powers / Patrilineage."""
        c = TarotCard()
        c.name = "Animal Powers"
        c.arcana = TarotCard.MAJOR
        c.levity_qualifier = "Centrifugal"
        c.gravity_qualifier = "Centripetal"
        c.reversal_qualifier = "Patrilineage"  # name swap, NOT a qualifier
        # Levity emanation — upright + polarity qualifier
        face = c.applet_face("levity", reversed=False)
        self.assertEqual(face["title"], "Animal Powers,")
        self.assertEqual(face["qualifier"], "Centrifugal")
        # Levity reversal — name swap + polarity qualifier persists
        face = c.applet_face("levity", reversed=True)
        self.assertEqual(face["title"], "Patrilineage,")
        self.assertEqual(face["qualifier"], "Centrifugal")
        # Gravity emanation
        face = c.applet_face("gravity", reversed=False)
        self.assertEqual(face["title"], "Animal Powers,")
        self.assertEqual(face["qualifier"], "Centripetal")
        # Gravity reversal — name swap + polarity qualifier
        face = c.applet_face("gravity", reversed=True)
        self.assertEqual(face["title"], "Patrilineage,")
        self.assertEqual(face["qualifier"], "Centripetal")

    def test_pattern_b_prime_major_reversal_drops_qualifier(self):
        """User-spec 2026-05-23: cards 16-18 (Realms) — same Pattern B name
        swap, but the reversal face renders the name ALONE w. no qualifier.
        Marked via `reversal_drops_qualifier=True`."""
        c = TarotCard()
        c.name = "Disco Inferno"
        c.arcana = TarotCard.MAJOR
        c.levity_qualifier = "Deasil"
        c.gravity_qualifier = "Widdershins"
        c.reversal_qualifier = "Shame"
        c.reversal_drops_qualifier = True
        # Upright unchanged (polarity qualifier on the upright face)
        face = c.applet_face("levity", reversed=False)
        self.assertEqual(face["title"], "Disco Inferno,")
        self.assertEqual(face["qualifier"], "Deasil")
        # Reversal: name swap ALONE, no qualifier
        face = c.applet_face("levity", reversed=True)
        self.assertEqual(face["title"], "Shame")
        self.assertEqual(face["qualifier"], "")
        face = c.applet_face("gravity", reversed=True)
        self.assertEqual(face["title"], "Shame")
        self.assertEqual(face["qualifier"], "")

    def test_non_major_with_reversal_qualifier_still_renders_as_qualifier(self):
        """Regression pin — Queen of Crowns (middle court) uses `reversal_
        qualifier` as the QUALIFIER on the reversal face (not a name swap).
        Pattern B / B' must NOT bleed into non-Majors."""
        c = TarotCard()
        c.name = "Queen of Crowns"
        c.arcana = TarotCard.MIDDLE
        c.levity_qualifier = "Elevated"
        c.gravity_qualifier = "Graven"
        c.reversal_qualifier = "Vacant"
        face = c.applet_face("levity", reversed=True)
        # Title stays as the original name; reversal_qualifier is the qualifier
        self.assertEqual(face["title"], "Queen of Crowns")
        self.assertEqual(face["qualifier"], "Vacant")
        self.assertTrue(face["qualifier_first"])  # non-Major: qualifier above

    def test_major_without_qualifier_renders_qualifier_first_for_blank(self):
        # Major 0 (Nomad) — no levity/gravity qualifier. Qualifier-first
        # branch is taken (since `qualifier_first` is the default when no
        # Major+qualifier check passes); qualifier text is blank so the
        # `:empty` CSS hides the slot.
        c = TarotCard()
        c.name = "Group 3: The Nomad"
        c.arcana = TarotCard.MAJOR
        face = c.applet_face("gravity", reversed=False)
        self.assertEqual(face["title"], "The Nomad")
        self.assertEqual(face["qualifier"], "")
        self.assertTrue(face["qualifier_first"])


class TarotCardNameSplitTest(SimpleTestCase):
    """TarotCard.name_group / name_title — colon-split parsing."""

    def test_name_group_returns_prefix_with_colon(self):
        c = TarotCard()
        c.name = 'Group 3: The Nomad'
        self.assertEqual(c.name_group, 'Group 3:')

    def test_name_group_empty_when_no_colon(self):
        c = TarotCard()
        c.name = 'Jack of Brands'
        self.assertEqual(c.name_group, '')

    def test_name_title_returns_suffix_after_colon(self):
        c = TarotCard()
        c.name = 'Group 3: The Nomad'
        self.assertEqual(c.name_title, 'The Nomad')

    def test_name_title_returns_full_name_when_no_colon(self):
        c = TarotCard()
        c.name = 'Jack of Brands'
        self.assertEqual(c.name_title, 'Jack of Brands')


class TarotCardCautionsJsonTest(SimpleTestCase):
    """TarotCard.cautions_json — JSON-serialised cautions list."""

    def test_empty_cautions_serialises_to_empty_list(self):
        c = TarotCard()
        c.cautions = []
        self.assertEqual(c.cautions_json, '[]')

    def test_cautions_serialise_to_json_array(self):
        c = TarotCard()
        c.cautions = [{'type': 'Vanity', 'effect': 'Costs an Ardor token.'}]
        self.assertEqual(json.loads(c.cautions_json), c.cautions)
