import uuid
from datetime import datetime
from typing import Dict, List, Optional, Any
from natsort import natsorted
from sqlalchemy import ForeignKey, func
from sqlalchemy.orm import relationship, Mapped, mapped_column

from src.db.models.base import Base, BaseMixin


class Publisher(Base):
    __tablename__ = "publisher"
    id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
    created_date: Mapped[datetime] = mapped_column(default=func.now())
    last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
    version: Mapped[int] = mapped_column(default=0)
    name: Mapped[str] = mapped_column(unique=True)
    weblink: Mapped[Optional[str]]
    parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
    parent_publisher: Mapped[Optional['Publisher']] = relationship(back_populates="imprints", remote_side=[id])
    imprints: Mapped[List['Publisher']] = relationship(back_populates="parent_publisher")
    comics: Mapped[List["Comic"]] = relationship(back_populates="publisher")

    def __repr__(self):
        return f'Publisher({self.id} {self.name})'

    def __str__(self):
        return self.__repr__()


class Comic(Base, BaseMixin):
    __tablename__ = 'comic'
    title: Mapped[str] = mapped_column(unique=True)
    publisher_id: Mapped[str] = mapped_column(ForeignKey('publisher.id'), nullable=False)
    publisher: Mapped[Publisher] = relationship(back_populates="comics")
    current_order: Mapped[bool]
    completed: Mapped[bool]
    weblink: Mapped[Optional[str]]
    issues: Mapped[List["Issue"]] = relationship(back_populates="comic", order_by="Issue.issue_number")
    story_arcs: Mapped[List["StoryArc"]] = relationship(back_populates="comic")
    trade_paperbacks: Mapped[List["TradePaperback"]] = relationship(back_populates="comic")
    volumes: Mapped[List["Volume"]] = relationship(back_populates="comic")
    comic_works: Mapped[List["ComicWork"]] = relationship(back_populates="comic")

    def __repr__(self):
        return f'Comic({self.id} {self.version} {self.title} {self.publisher.name})'

    def __str__(self):
        return f'{self.title}({self.id})'

    def get_artists(self) -> Dict[Any, List[Any]]:
        works: Dict[Any, List[Any]] = {}
        for work in self.comic_works:
            work_type = work.work_type
            artist = work.artist
            if work_type in works:
                works[work_type].append(artist)
            else:
                works[work_type] = [artist]
        return works

    def sorted_issues(self):
        sorted_issues = natsorted(self.issues, key=lambda x: getattr(x, 'issue_number'))
        return sorted_issues


class Volume(Base, BaseMixin):
    __tablename__ = "volume"
    name: Mapped[str] = mapped_column(nullable=False)
    comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False)
    comic: Mapped[Comic] = relationship(back_populates="volumes")
    story_arcs: Mapped[List["StoryArc"]] = relationship(back_populates="volume")
    issues: Mapped[List["Issue"]] = relationship(back_populates="volume")


class TradePaperback(Base, BaseMixin):
    __tablename__ = "trade_paperback"
    name: Mapped[str] = mapped_column(nullable=False)
    issue_start: Mapped[int]
    issue_end: Mapped[int]
    comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False)
    comic: Mapped[Comic] = relationship(back_populates="trade_paperbacks")


class StoryArc(Base, BaseMixin):
    __tablename__ = "story_arc"
    name: Mapped[str] = mapped_column(nullable=False)
    comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False)
    comic: Mapped[Comic] = relationship(back_populates="story_arcs")
    volume_id: Mapped[str] = mapped_column(ForeignKey("volume.id"), nullable=True)
    volume: Mapped[Volume] = relationship(back_populates="story_arcs")
    issues: Mapped[List["Issue"]] = relationship(back_populates="story_arc")


class Issue(Base, BaseMixin):
    __tablename__ = "issue"
    issue_number: Mapped[str]
    title: Mapped[Optional[str]]
    published_on: Mapped[Optional[datetime]]
    in_stock: Mapped[bool]
    is_read: Mapped[bool]
    comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False)
    comic: Mapped[Comic] = relationship(back_populates="issues")
    volume_id: Mapped[str] = mapped_column(ForeignKey("volume.id"), nullable=True)
    volume: Mapped[Volume] = relationship(back_populates="issues")
    story_arc_id: Mapped[str] = mapped_column(ForeignKey("story_arc.id"), nullable=True)
    story_arc: Mapped[StoryArc] = relationship(back_populates="issues")
    issue_works: Mapped[List["IssueWork"]] = relationship(back_populates="issue")

    def get_full_title(self) -> str:
        full_title: str = str(self.issue_number)
        if self.title:
            full_title += str(": " + self.title)
        return full_title

    def get_artists(self) -> Dict[Any, List[Any]]:
        works: Dict[Any, List[Any]] = {}
        for work in self.issue_works:
            work_type = work.work_type
            artist = work.artist
            if work_type in works:
                works[work_type].append(artist)
            else:
                works[work_type] = [artist]
        return works


class Artist(Base, BaseMixin):
    __tablename__ = "artist"
    name: Mapped[str] = mapped_column(nullable=False)
    weblink: Mapped[str] = mapped_column(nullable=True)
    comic_works: Mapped[List["ComicWork"]] = relationship(back_populates="artist")
    issue_works: Mapped[List["IssueWork"]] = relationship(back_populates="artist")

    def get_comics(self) -> Dict[Any, List[Comic]]:
        works: Dict[Any, List[Comic]] = {}
        for work in self.comic_works:
            work_type = work.work_type
            comic = work.comic
            if work_type in works:
                works[work_type].append(comic)
            else:
                works[work_type] = [comic]
        return works


class WorkType(Base, BaseMixin):
    __tablename__ = "worktype"
    name: Mapped[str] = mapped_column(nullable=False, unique=True)
    comic_works: Mapped[List["ComicWork"]] = relationship(back_populates="work_type")
    issue_works: Mapped[List["IssueWork"]] = relationship(back_populates="work_type")

    def get_artists(self) -> Dict[str, List[Artist]]:
        works: Dict[str, List[Artist]] = {}
        for work in self.comic_works:
            comic = work.comic.title
            artist = work.artist
            if comic in works:
                works[comic].append(artist)
            else:
                works[comic] = [artist]
        return works

    def __repr__(self):
        return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'

    def __str__(self):
        return f'{self.name}({self.id})'


class ComicWork(Base, BaseMixin):
    __tablename__ = "comic_work"
    comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False)
    comic: Mapped[Comic] = relationship(back_populates="comic_works")
    artist_id: Mapped[str] = mapped_column(ForeignKey("artist.id"), nullable=False)
    artist: Mapped[Artist] = relationship(back_populates="comic_works")
    work_type_id: Mapped[str] = mapped_column(ForeignKey("worktype.id"), nullable=False)
    work_type: Mapped[WorkType] = relationship(back_populates="comic_works")


class IssueWork(Base, BaseMixin):
    __tablename__ = "issue_work"
    issue_id: Mapped[str] = mapped_column(ForeignKey("issue.id"), nullable=False)
    issue: Mapped[Issue] = relationship(back_populates="issue_works")
    artist_id: Mapped[str] = mapped_column(ForeignKey("artist.id"), nullable=False)
    artist: Mapped[Artist] = relationship(back_populates="issue_works")
    work_type_id: Mapped[str] = mapped_column(ForeignKey("worktype.id"), nullable=False)
    work_type: Mapped[WorkType] = relationship(back_populates="issue_works")
