import uuid
from datetime import datetime
from typing import AnyStr, Dict, List, Optional, Any
from natsort import natsorted
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, 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 = Column(String, unique=True)
    weblink = Column(String, nullable=True)
    parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
    parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
    imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
    comics = relationship("Comic")

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

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


class Comic(Base, BaseMixin):
    __tablename__ = 'comic'
    title = Column(String, unique=True)
    publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
    publisher = relationship("Publisher", back_populates="comics")
    current_order = Column(Boolean)
    completed = Column(Boolean)
    weblink = Column(String, nullable=True)
    issues = relationship("Issue", order_by="Issue.issue_number")
    story_arcs = relationship("StoryArc")
    trade_paperbacks = relationship("TradePaperback")
    volumes = relationship("Volume")
    comic_works = relationship("ComicWork")

    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 = Column(String, nullable=False)
    comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
    comic = relationship("Comic", back_populates="volumes")
    story_arcs = relationship("StoryArc")
    issues = relationship("Issue")


class TradePaperback(Base, BaseMixin):
    __tablename__ = "trade_paperback"
    name = Column(String, nullable=False)
    issue_start = Column(Integer)
    issue_end = Column(Integer)
    comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
    comic = relationship("Comic", back_populates="trade_paperbacks")


class StoryArc(Base, BaseMixin):
    __tablename__ = "story_arc"
    name = Column(String, nullable=False)
    comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
    comic = relationship("Comic", back_populates="story_arcs")
    volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
    volume = relationship("Volume", back_populates="story_arcs")
    issues = relationship("Issue")


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

    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 = Column(String, nullable=False)
    weblink = Column(String, nullable=True)
    comic_works = relationship("ComicWork")
    issue_works = relationship("IssueWork")

    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 = Column(String, nullable=False, unique=True)
    comic_works = relationship("ComicWork")
    issue_works = relationship("IssueWork")

    def get_artists(self) -> Dict[str, List[str]]:
        works: Dict[str, List[str]] = {}
        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 = Column(String, ForeignKey("comic.id"), nullable=False)
    comic = relationship("Comic", back_populates="comic_works")
    artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
    artist = relationship("Artist", back_populates="comic_works")
    work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
    work_type = relationship("WorkType", back_populates="comic_works")


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