import json
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from ttfrog.db import schema
from ttfrog.db.manager import db as _db
from ttfrog.db.schema.constants import DamageType, Defenses

FIXTURE_PATH = Path(__file__).parent / "fixtures"


def load_fixture(db, fixture_name):
    with db.transaction():
        data = json.loads((FIXTURE_PATH / f"{fixture_name}.json").read_text())
        for schema_name in data:
            for record in data[schema_name]:
                print(f"Loading {schema_name} {record = }")
                obj = getattr(schema, schema_name)(**record)
                db.session.add(obj)


@pytest.fixture(autouse=True)
def db(monkeypatch):
    monkeypatch.setattr("ttfrog.db.manager.database", MagicMock(return_value=""))
    monkeypatch.setenv("DATABASE_URL", "sqlite:///:memory:")
    monkeypatch.setenv("DEBUG", "1")
    _db.init()
    yield _db
    _db.metadata.drop_all(bind=_db.engine)


@pytest.fixture
def bootstrap(db):
    with db.transaction():
        # ancestries
        human = schema.Ancestry("human")

        tiefling = schema.Ancestry("tiefling")
        tiefling.add_modifier(
            schema.Modifier("Ability Score Increase", target="intelligence", stacks=True, relative_value=1)
        )
        tiefling.add_modifier(
            schema.Modifier("Ability Score Increase", target="charisma", stacks=True, relative_value=2)
        )

        # ancestry traits
        darkvision = schema.AncestryTrait("Darkvision")
        darkvision.add_modifier(schema.Modifier("Darkvision", target="vision_in_darkness", absolute_value=120))
        tiefling.add_trait(darkvision)

        # resistant to fire
        infernal_origin = schema.AncestryTrait("Infernal Origin")
        infernal_origin.add_modifier(
            schema.Modifier("Infernal Origin", target=DamageType.fire, new_value=Defenses.resistant)
        )
        tiefling.add_trait(infernal_origin)

        dragonborn = schema.Ancestry("dragonborn")
        dragonborn.add_trait(darkvision)

        db.add_or_update([human, dragonborn, tiefling])

        # skills
        skills = {
            name: schema.Skill(name=name)
            for name in ("strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma")
        }
        db.add_or_update(list(skills.values()))
        acrobatics = schema.Skill(name="Acrobatics", base_id=skills["dexterity"].id)
        athletics = schema.Skill(name="Athletics", base_id=skills["strength"].id)
        db.add_or_update([acrobatics, athletics])

        # classes
        fighting_style = schema.ClassFeature("Fighting Style")
        fighting_style.add_option(name="Archery")
        fighting_style.add_option(name="Defense")
        db.add_or_update(fighting_style)

        fighter = schema.CharacterClass(
            "fighter", hit_die_name="1d10", hit_die_stat_name="_constitution", starting_skills=2
        )
        db.add_or_update(fighter)

        # add skills
        fighter.add_skill(acrobatics)
        fighter.add_skill(athletics)
        fighter.add_feature(fighting_style, level=2)
        db.add_or_update(fighter)
        assert acrobatics in fighter.skills
        assert athletics in fighter.skills

        wizard = schema.CharacterClass(
            "wizard",
            hit_die_name="1d6",
            hit_die_stat_name="_intelligence",
        )
        db.add_or_update(wizard)
        wizard.spell_slots = [
            schema.ClassSpellSlotMap(wizard.id, class_level=1, spell_level=1),
            schema.ClassSpellSlotMap(wizard.id, class_level=1, spell_level=1),
            schema.ClassSpellSlotMap(wizard.id, class_level=2, spell_level=1),
            schema.ClassSpellSlotMap(wizard.id, class_level=3, spell_level=1),
            schema.ClassSpellSlotMap(wizard.id, class_level=3, spell_level=2),
            schema.ClassSpellSlotMap(wizard.id, class_level=3, spell_level=2),
            schema.ClassSpellSlotMap(wizard.id, class_level=4, spell_level=2),
            schema.ClassSpellSlotMap(wizard.id, class_level=5, spell_level=3),
            schema.ClassSpellSlotMap(wizard.id, class_level=5, spell_level=3),
            schema.ClassSpellSlotMap(wizard.id, class_level=6, spell_level=3),
            schema.ClassSpellSlotMap(wizard.id, class_level=7, spell_level=4),
        ]

        rogue = schema.CharacterClass("rogue", hit_die_name="1d8", hit_die_stat_name="_dexterity")
        db.add_or_update([rogue, fighter, wizard])

        # create a character: Carl the Wizard
        carl = schema.Character("Carl", ancestry=tiefling, _intelligence=14)
        carl.add_class(wizard)
        db.add_or_update(carl)


@pytest.fixture
def carl(db, bootstrap):
    return db.Character.filter_by(name="Carl").one()


@pytest.fixture
def wizard(db, bootstrap):
    return db.CharacterClass.filter_by(name="wizard").one()


@pytest.fixture
def tiefling(db, bootstrap):
    return db.Ancestry.filter_by(name="tiefling").one()


@pytest.fixture
def human(db, bootstrap):
    return db.Ancestry.filter_by(name="human").one()
