import datetime
import uuid
from dataclasses import dataclass, field
from enum import Enum

import sqlalchemy
from sqlalchemy import DateTime, ForeignKey, UniqueConstraint, create_engine, event, func, select, CheckConstraint
from sqlalchemy import Enum as SAEnum
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, Session, mapped_column, relationship

from idlemaxx.models.items import Item
from idlemaxx.models.skills import Skill


def now_utc() -> datetime.datetime:
    return datetime.datetime.now(tz=datetime.UTC).replace(tzinfo=None)


def xp_for_level(level: int) -> int:
    total = 0
    for i in range(1, level):
        total += int(i + 300 * 2 ** (i / 7.0))
    return total // 4


def level_from_xp(xp: int) -> int:
    level = 1
    while xp_for_level(level + 1) <= xp:
        level += 1
    return level


class IdlemaxBase(MappedAsDataclass, DeclarativeBase):
    __abstract__ = True
    id: Mapped[uuid.UUID] = mapped_column(default_factory=uuid.uuid4, primary_key=True, init=False)
    created_at: Mapped[datetime.datetime] = mapped_column(default_factory=now_utc, init=False)
    updated_at: Mapped[datetime.datetime] = mapped_column(default_factory=now_utc, onupdate=now_utc, init=False)


class Character(IdlemaxBase):
    __tablename__ = "characters"
    name: Mapped[str] = mapped_column(unique=True)
    discord_user_id: Mapped[str | None] = mapped_column(default=None)

    skills: Mapped[list["CharacterSkill"]] = relationship(default_factory=list, cascade="all, delete-orphan")
    activities: Mapped[list["CharacterActivity"]] = relationship(default_factory=list, cascade="all, delete-orphan")
    items: Mapped[list["CharacterItem"]] = relationship(default_factory=list, cascade="all, delete-orphan")
    activity_queue: Mapped[list["CharacterActivityQueue"]] = relationship(
        default_factory=list, cascade="all, delete-orphan", order_by="CharacterActivityQueue.created_at"
    )

    @property
    def current_activity(self) -> "CharacterActivity | None":
        for activity in self.activities:
            if activity.ended_at is None:
                return activity
        return None


class CharacterSkill(IdlemaxBase):
    __tablename__ = "character_skills"
    character_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("characters.id"))
    skill: Mapped[Skill] = mapped_column(SAEnum(Skill))
    experience: Mapped[int] = mapped_column(default=0)

    __table_args__ = (
        UniqueConstraint("character_id", "skill", name="character_id_skill_uc"),
        CheckConstraint("experience >= 0", name="experience_non_negative"),
    )


class CharacterItem(IdlemaxBase):
    __tablename__ = "character_items"
    character_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("characters.id"))
    item: Mapped[Item] = mapped_column(SAEnum(Item))
    quantity: Mapped[int] = mapped_column(default=0)

    __table_args__ = (
        UniqueConstraint("character_id", "item", name="character_id_item_uc"),
        CheckConstraint("quantity >= 0", name="quantity_non_negative"),
    )


@dataclass
class ActivitySkillRequirement:
    skill: Skill
    required_level: int


@dataclass
class ActivityMaterial:
    item: Item
    quantity: int


@dataclass
class ActivitySkillReward:
    skill: Skill
    xp: int


@dataclass
class ActivityItemReward:
    item: Item
    quantity: int


@dataclass
class ActivityCategoryData:
    display_name: str
    icon: str
    abbreviation: str


class ActivityCategory(Enum):
    WOODCUTTING = ActivityCategoryData(display_name="Woodcutting", icon="🪓", abbreviation="wc")
    MINING = ActivityCategoryData(display_name="Mining", icon="💎", abbreviation="min")
    FISHING = ActivityCategoryData(display_name="Fishing", icon="🎣", abbreviation="fish")
    SMELTING = ActivityCategoryData(display_name="Smelting", icon="🔥", abbreviation="smelt")

    @property
    def display_name(self) -> str:
        return self.value.display_name

    @property
    def icon(self) -> str:
        return self.value.icon

    @property
    def abbreviation(self) -> str:
        return self.value.abbreviation


@dataclass
class ActivityData:
    name: str
    action_time: float
    category: ActivityCategory
    skill_requirements: list[ActivitySkillRequirement] = field(default_factory=list)
    materials: list[ActivityMaterial] = field(default_factory=list)
    skill_rewards: list[ActivitySkillReward] = field(default_factory=list)
    item_rewards: list[ActivityItemReward] = field(default_factory=list)


# todo: Fill out existing activities' requirements and rewards
class Activity(Enum):
    CHOP_REGULAR_TREE = ActivityData(
        name="chop_tree",
        action_time=0.5,
        category=ActivityCategory.WOODCUTTING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.WOODCUTTING, required_level=1),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.WOODCUTTING, xp=1),
        ],
        item_rewards=[
            ActivityItemReward(item=Item.REGULAR_LOG, quantity=1),
        ],
    )
    CHOP_OAK = ActivityData(
        name="chop_oak",
        action_time=1,
        category=ActivityCategory.WOODCUTTING,
        skill_requirements=[
            ActivitySkillRequirement(Skill.WOODCUTTING, required_level=15),
        ],
        skill_rewards=[
            ActivitySkillReward(Skill.WOODCUTTING, xp=3),
        ],
        item_rewards=[
            ActivityItemReward(Item.OAK_LOG, quantity=1),
        ],
    )
    CHOP_REDWOOD = ActivityData(
        name="chop_redwood",
        action_time=5,
        category=ActivityCategory.WOODCUTTING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.WOODCUTTING, required_level=80),
        ],
        skill_rewards=[
            ActivitySkillReward(Skill.WOODCUTTING, xp=10),
        ],
        item_rewards=[
            ActivityItemReward(Item.REDWOOD_LOG, quantity=2),
        ],
    )
    MINE_TIN = ActivityData(
        name="mine_tin",
        action_time=1,
        category=ActivityCategory.MINING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.MINING, required_level=1),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.MINING, xp=1),
        ],
        item_rewards=[
            ActivityItemReward(item=Item.TIN_ORE, quantity=1),
        ],
    )
    MINE_COPPER = ActivityData(
        name="mine_copper",
        action_time=1,
        category=ActivityCategory.MINING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.MINING, required_level=1),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.MINING, xp=1),
        ],
        item_rewards=[
            ActivityItemReward(item=Item.COPPER_ORE, quantity=1),
        ],
    )
    MINE_IRON = ActivityData(
        name="mine_iron",
        action_time=1.5,
        category=ActivityCategory.MINING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.MINING, required_level=1),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.MINING, xp=2),
        ],
        item_rewards=[
            ActivityItemReward(item=Item.IRON_ORE, quantity=1),
        ],
    )
    MINE_COBALT = ActivityData(
        name="mine_cobalt",
        action_time=3,
        category=ActivityCategory.MINING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.MINING, required_level=40),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.MINING, xp=5),
        ],
        item_rewards=[
            ActivityItemReward(Item.COBALT_ORE, quantity=1),
        ],
    )
    FISH_SHRIMP = ActivityData(
        name="fish_shrimp",
        action_time=0.5,
        category=ActivityCategory.FISHING,
        skill_requirements=[
            ActivitySkillRequirement(Skill.FISHING, required_level=1),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.FISHING, xp=1),
        ],
        item_rewards=[
            ActivityItemReward(Item.RAW_SHRIMP, quantity=1),
        ],
    )
    FISH_SHARK = ActivityData(
        name="fish_shark",
        action_time=7,
        category=ActivityCategory.FISHING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.FISHING, required_level=40),
        ],
        skill_rewards=[
            ActivitySkillReward(Skill.FISHING, xp=5),
        ],
        item_rewards=[
            ActivityItemReward(Item.RAW_SHARK, quantity=1),
        ],
    )
    SMELT_COPPER_BAR = ActivityData(
        name="smelt_copper_bar",
        action_time=5,
        category=ActivityCategory.SMELTING,
        skill_requirements=[
            ActivitySkillRequirement(skill=Skill.SMELTING, required_level=1),
        ],
        materials=[
            ActivityMaterial(item=Item.COPPER_ORE, quantity=1),
            ActivityMaterial(item=Item.TIN_ORE, quantity=1),
        ],
        skill_rewards=[
            ActivitySkillReward(skill=Skill.SMELTING, xp=1),
        ],
        item_rewards=[
            ActivityItemReward(item=Item.COPPER_BAR, quantity=1),
        ],
    )

    @property
    def category(self) -> ActivityCategory:
        return self.value.category

    @property
    def action_time(self) -> float:
        return self.value.action_time

    @property
    def skill_requirements(self) -> list[ActivitySkillRequirement]:
        return self.value.skill_requirements

    @property
    def skill_rewards(self) -> list[ActivitySkillReward]:
        return self.value.skill_rewards

    @property
    def item_rewards(self) -> list[ActivityItemReward]:
        return self.value.item_rewards

    @property
    def materials(self) -> list[ActivityMaterial]:
        return self.value.materials

    @classmethod
    def from_name(cls, name: str) -> "Activity":
        for activity in cls:
            if activity.value.name == name:
                return activity
        raise ValueError(f"No activity named {name}")


class CharacterActivity(IdlemaxBase):
    __tablename__ = "character_activities"
    character_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("characters.id"))
    activity: Mapped[Activity] = mapped_column(SAEnum(Activity))
    action_limit: Mapped[int | None] = mapped_column(default=None)
    started_at: Mapped[datetime.datetime] = mapped_column(default_factory=now_utc, init=False)
    ended_at: Mapped[datetime.datetime | None] = mapped_column(default=None, init=False)

    xp_rewards: Mapped[list["CharacterActivityXpReward"]] = relationship(
        default_factory=list, cascade="all, delete-orphan"
    )
    item_rewards: Mapped[list["CharacterActivityItemReward"]] = relationship(
        default_factory=list, cascade="all, delete-orphan"
    )
    materials_consumed: Mapped[list["CharacterActivityMaterialConsumed"]] = relationship(
        default_factory=list, cascade="all, delete-orphan"
    )

    __table_args__ = (CheckConstraint("ended_at is null or started_at <= ended_at", name="started_before_end"),)


class CharacterActivityXpReward(IdlemaxBase):
    __tablename__ = "character_activity_xp_rewards"
    character_activity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("character_activities.id"))

    skill: Mapped[Skill] = mapped_column(SAEnum(Skill))
    xp_gained: Mapped[int]


class CharacterActivityItemReward(IdlemaxBase):
    __tablename__ = "character_activity_item_rewards"
    character_activity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("character_activities.id"))

    item: Mapped[Item] = mapped_column(SAEnum(Item))
    quantity: Mapped[int]


class CharacterActivityMaterialConsumed(IdlemaxBase):
    __tablename__ = "character_activity_materials_consumed"

    character_activity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("character_activities.id"))
    item: Mapped[Item] = mapped_column(SAEnum(Item))
    quantity: Mapped[int]


class CharacterActivityQueue(IdlemaxBase):
    __tablename__ = "character_activity_queue"
    character_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("characters.id"))
    activity: Mapped[Activity] = mapped_column(SAEnum(Activity))
    action_limit: Mapped[int | None] = mapped_column(default=None)
    started_at: Mapped[datetime.datetime | None] = mapped_column(default=None, init=False)
    # created_at (from IdlemaxBase) = queued_at; ordered by created_at ASC
    # started_at IS NULL  → pending; IS NOT NULL → picked up
