import datetime
import typing
import sqlmodel as sql
from ebookwords.adapter.db import models


class SqlEbook(sql.SQLModel, table=True):
    __tablename__ = "ebook"  # type: ignore

    id: int | None = sql.Field(default=None, primary_key=True)
    date_added: datetime.datetime = sql.Field(default_factory=datetime.datetime.now)
    title: str


class SqlChapter(sql.SQLModel, table=True):
    __tablename__ = "chapter"  # type: ignore

    id: int | None = sql.Field(default=None, primary_key=True)
    ebook_id: int | None = sql.Field(default=None, foreign_key="ebook.id")
    chapter_number: int
    title: str


class SqlParagraph(sql.SQLModel, table=True):
    __tablename__ = "paragraph"  # type: ignore

    id: int | None = sql.Field(default=None, primary_key=True)
    chapter_id: int | None = sql.Field(default=None, foreign_key="chapter.id")
    paragraph_number: int
    text: str


class SqlDatabaseClient:
    def __init__(self, database_path: str) -> None:
        database_url = f"sqlite:///{database_path}"
        self.engine = sql.create_engine(database_url, echo=True)
        sql.SQLModel.metadata.create_all(self.engine)

    def add_book(self, ebook: models.Book) -> None:
        with sql.Session(self.engine) as session:
            ebook_item = SqlEbook(title=ebook.title)
            session.add(ebook_item)
            session.commit()

            for i, chapter in enumerate(ebook.chapters):
                chapter_item = SqlChapter(
                    ebook_id=ebook_item.id,
                    chapter_number=i,
                    title=chapter.title,
                )
                session.add(chapter_item)
                session.commit()

                for j, paragraph in enumerate(chapter.paragraphs):
                    paragraph_item = SqlParagraph(
                        chapter_id=chapter_item.id,
                        paragraph_number=j,
                        text=paragraph,
                    )
                    session.add(paragraph_item)
                session.commit()

    def get_book_list(self) -> typing.Iterator[models.BookRecord]:
        with sql.Session(self.engine) as session:
            statement = sql.select(SqlEbook)
            results = session.exec(statement)
            for ebook_item in results:
                yield models.BookRecord(
                    id=ebook_item.id or -1,
                    date_added=ebook_item.date_added,
                    title=ebook_item.title,
                )

    def get_chapter_list(self, ebook_id: int) -> typing.Iterator[models.ChapterRecord]:
        with sql.Session(self.engine) as session:
            statement = sql.select(SqlChapter.chapter_number, SqlChapter.title)
            statement = statement.where(SqlChapter.ebook_id == ebook_id)
            results = session.exec(statement)

            for chapter_number, chapter_title in results:
                yield models.ChapterRecord(
                    chapter_number=chapter_number,
                    title=chapter_title,
                )

    def get_book(self, ebook_id: int) -> models.BookRecord | None:
        with sql.Session(self.engine) as session:
            statement = sql.select(SqlEbook)
            statement = statement.where(SqlEbook.id == ebook_id)
            results = session.exec(statement)
            ebook_item = results.first()
            if not ebook_item:
                return None
            return models.BookRecord(
                id=ebook_id,
                date_added=ebook_item.date_added,
                title=ebook_item.title,
            )

    def get_chapter_paragraphs(
        self, ebook_id: int, chapter_number: int
    ) -> typing.Iterator[str]:
        with sql.Session(self.engine) as session:
            statement = sql.select(SqlParagraph.text)
            statement = statement.where(SqlParagraph.chapter_id == SqlChapter.id)
            statement = statement.where(SqlChapter.ebook_id == ebook_id)
            statement = statement.where(SqlChapter.chapter_number == chapter_number)
            results = session.exec(statement)
            return results
