import datetime
import logging
import contextlib

from typing import Optional

import pygments.lexers

from sqlalchemy import (
    Integer,
    Column,
    String,
    DateTime,
    Text,
    ForeignKey,
    create_engine,
)
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.orm.session import Session
from sqlalchemy.ext.declarative import declarative_base, declared_attr

from pygments_better_html import BetterHtmlFormatter

from pinnwand import configuration, error, utility


log = logging.getLogger(__name__)

_engine = create_engine(configuration.database_uri)
_session = sessionmaker(bind=_engine)


@contextlib.contextmanager
def session() -> Session:
    a_session = _session()

    try:
        yield a_session
    except Exception:
        a_session.rollback()
        raise
    finally:
        a_session.close()


class _Base(object):
    """Base class which provides automated table names
    and a primary key column."""

    @declared_attr
    def __tablename__(cls) -> str:  # pylint: disable=no-self-argument
        return str(cls.__name__.lower())  # type: ignore

    id = Column(Integer, primary_key=True)


Base = declarative_base(cls=_Base)


class Paste(Base):  # type: ignore
    """The Paste model represents a single Paste."""

    pub_date = Column(DateTime)
    chg_date = Column(DateTime)

    slug = Column(String(250), unique=True)
    removal = Column(String(250), unique=True)

    src = Column(String(250))

    exp_date = Column(DateTime)

    files = relationship("File", cascade="all,delete", backref="paste")

    def __init__(
        self,
        slug: str,
        expiry: datetime.timedelta = datetime.timedelta(days=7),
        src: str = None,
    ) -> None:
        # Generate a paste_id and a removal_id
        # Unless someone proves me wrong that I need to check for collisions
        # my famous last words will be that the odds are astronomically small
        self.slug = slug
        self.removal = utility.slug_create(auto_scale=False)

        self.pub_date = datetime.datetime.utcnow()
        self.chg_date = datetime.datetime.utcnow()

        self.src = src

        # The expires date is the pub_date with the delta of the expiry
        if expiry:
            self.exp_date = self.pub_date + expiry
        else:
            self.exp_date = None

    def __repr__(self) -> str:
        return f"<Paste(slug={self.slug})>"


class File(Base):  # type: ignore
    paste_id = Column(ForeignKey("paste.id"))
    slug = Column(String(255), unique=True)

    pub_date = Column(DateTime)
    chg_date = Column(DateTime)

    lexer = Column(String(250))

    raw = Column(Text(configuration.paste_size))
    fmt = Column(Text(configuration.paste_size))

    filename = Column(String(250))

    def __init__(
        self,
        slug: str,
        raw: str,
        lexer: str = "text",
        filename: Optional[str] = None,
    ) -> None:
        # Start with some basic housekeeping related to size
        if len(raw) > configuration.paste_size:
            raise error.ValidationError(
                f"Text exceeds size limit {configuration.paste_size//1024} (kB)"
            )

        self.pub_date = datetime.datetime.utcnow()
        self.chg_date = datetime.datetime.utcnow()

        self.raw = raw

        self.filename = filename

        self.lexer = lexer

        if lexer == 'autodetect':
            lexer = utility.guess_language(raw, filename)
            log.debug(f"Language guessed as {lexer}")

        lexer = pygments.lexers.get_lexer_by_name(lexer)
        formatter = BetterHtmlFormatter(  # pylint: disable=no-member
            linenos="table", cssclass="source"
        )

        formatted = pygments.highlight(self.raw, lexer, formatter)

        if len(formatted) >= configuration.paste_size:
            raise error.ValidationError(
                f"Highlighted text exceeds size limit ({configuration.paste_size//1024} kB)"
            )

        self.fmt = formatted
        self.slug = slug

    @property
    def pretty_size(self) -> str:
        return utility.size_postfix(len(self.raw))