"""Database helpers for sentence persistence."""

from dataclasses import dataclass
from typing import Optional

import sqlalchemy

DB_NAME = "db.json"


class NoDatabaseSpecified(Exception):
    """No database connection was specified during init."""


class SentenceIdNotFound(Exception):
    """Could not find a previously generated sentence with specified ID."""


class DatabaseInsertFailed(Exception):
    """Raised when a sentence cannot be inserted into the database."""


@dataclass
class DatabaseConnection:
    """Stores info related to database connection."""

    engine: sqlalchemy.engine.Engine
    metadata: sqlalchemy.MetaData
    sentence_table: sqlalchemy.Table


def create_database_connection(
    voice_name: str,
    database: Optional[sqlalchemy.engine.Engine],
) -> Optional[DatabaseConnection]:
    if database is None:
        return None

    metadata = sqlalchemy.MetaData()
    sentence_table = sqlalchemy.Table(
        voice_name,
        metadata,
        sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
        sqlalchemy.Column("sentence", sqlalchemy.String, unique=True, nullable=False),
    )
    metadata.create_all(database)
    return DatabaseConnection(
        metadata=metadata, sentence_table=sentence_table, engine=database
    )


def insert_sentence_into_db(db: Optional[DatabaseConnection], sentence: str) -> int:
    if db is None:
        raise NoDatabaseSpecified

    insert_query = db.sentence_table.insert().values(sentence=sentence)
    with db.engine.begin() as conn:
        result = conn.execute(insert_query)

    if result.inserted_primary_key is None:
        raise DatabaseInsertFailed

    sentence_id = result.inserted_primary_key[0]
    if sentence_id is None:
        raise DatabaseInsertFailed

    return int(sentence_id)


def sentence_exists(db: Optional[DatabaseConnection], sentence: str) -> bool:
    if db is None:
        raise NoDatabaseSpecified

    select_query = db.sentence_table.select().where(
        db.sentence_table.c.sentence == sentence
    )
    with db.engine.connect() as conn:
        result = conn.execute(select_query)

    return bool(result.all())


def get_generated_sentences_list(db: Optional[DatabaseConnection]) -> list[str]:
    if db is None:
        raise NoDatabaseSpecified

    select_query = db.sentence_table.select()
    with db.engine.connect() as conn:
        result = conn.execute(select_query)

    return [row.sentence for row in result.all()]


def get_generated_sentences_dict(db: Optional[DatabaseConnection]) -> dict[int, str]:
    if db is None:
        raise NoDatabaseSpecified

    select_query = db.sentence_table.select()
    with db.engine.connect() as conn:
        result = conn.execute(select_query)

    return {row.id: row.sentence for row in result.all()}


def get_generated_sentence_from_id(
    db: Optional[DatabaseConnection], sentence_id: str
) -> Optional[str]:
    if db is None:
        raise NoDatabaseSpecified

    select_query = db.sentence_table.select().where(
        db.sentence_table.c.id == int(sentence_id)
    )
    with db.engine.connect() as conn:
        result = conn.execute(select_query)

    sentences = result.all()
    if not sentences:
        return None

    return str(sentences[0].sentence)
