import enum
import uuid
import datetime as dt
from enum import unique

from sqlalchemy import func, text, UniqueConstraint, MetaData
from sqlalchemy.orm import relationship, declarative_base
from sqlalchemy.schema import FetchedValue
# TODO from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, \
    Boolean, Enum, Text, JSON, Table
from sqlalchemy_utils import UUIDType


# Local state DB ###############################################################

LocalBase = declarative_base()

class WurkerHive(LocalBase):
    __tablename__ = "wurker_hive"

    wurker_bee_id = Column(Integer, nullable=False, primary_key=True)
    wurker_cron_id = Column(Integer, nullable=True, primary_key=True)
    pid = Column(Integer)
    runcount = Column(Integer, nullable=False, default=0)
    timestamp = Column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))


# Local Flask App DB ###########################################################

AppBase = declarative_base()

class User(AppBase):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    username = Column(String(256), nullable=False)
    password = Column(String(64), nullable=False)


class ApiAuth(AppBase):
    __tablename__ = "api_auth"

    id = Column(Integer, primary_key=True)
    username = Column(String(256), nullable=False)
    password = Column(String(64), nullable=False)


# Config DB models #############################################################

# Some global stuff
metadata = MetaData(naming_convention={
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_`%(constraint_name)s`",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s"
})

Base = declarative_base(metadata=metadata)

@unique
class WurkerBeeState(enum.IntEnum):
    PROCESSING = 0
    OVERRUN = 1
    TIMEOUT = 2
    ERROR = 3
    COMPLETED = 4


class WurkerControl(Base):
    __tablename__ = "wurker_control"

    app_id = Column(UUIDType(binary=True), primary_key=True, default=uuid.uuid4)
    seed = Column(Integer, default=-1)
    reload = Column(Boolean, default=False)
    pause = Column(Boolean, default=False)

    status = relationship('WurkerStatus', back_populates="control", uselist=False)


class WurkerStatus(Base):
    __tablename__ = "wurker_status"

    app_id = Column(UUIDType(binary=True), ForeignKey('wurker_control.app_id'), primary_key=True, nullable=False)
    slug = Column(String(36), nullable=False)
    name = Column(String(36), nullable=False)
    is_queen = Column(Boolean, default=True)
    env = Column(String(36), nullable=False)
    private_ip = Column(String(36), nullable=True)
    public_ip = Column(String(36), nullable=True)
    server_domain = Column(String(128), nullable=True)

    control = relationship('WurkerControl', back_populates='status')


class WurkerCommand(Base):
    __tablename__ = "wurker_command"

    id = Column(Integer, primary_key=True)
    app_id = Column(UUIDType(binary=True), ForeignKey('wurker_control.app_id'), nullable=False)
    slug = Column(String(36), nullable=False)
    name = Column(String(36), nullable=False)
    description = Column(Text)
    command = Column(String(128))
    is_module = Column(Boolean, default=False, nullable=False)

    bees = relationship('WurkerBee', back_populates='command')
    control = relationship('WurkerControl')

    __table_args__ = (UniqueConstraint("app_id", "slug"),)


wurker_bee_cron = Table(
    "wurker_bee_cron",
    Base.metadata,
    Column("wurker_bee_id", ForeignKey('wurker_bee.id', ondelete="CASCADE"), primary_key=True),
    Column("wurker_cron_id", ForeignKey('wurker_cron.id', ondelete="CASCADE"), primary_key=True)
)


class WurkerBee(Base):
    __tablename__ = "wurker_bee"

    id = Column(Integer, primary_key=True)
    app_id = Column(UUIDType(binary=True), ForeignKey('wurker_control.app_id'), nullable=False)
    slug = Column(String(128), nullable=False)
    name = Column(String(128), nullable=False)
    description = Column(Text)
    command_id = Column(Integer, ForeignKey('wurker_command.id'), nullable=False)
    args = Column(JSON)
    quiet_restart = Column(Boolean, default=False)
    enabled = Column(Boolean, default=True, nullable=False)

    command = relationship('WurkerCommand', back_populates='bees')
    crons = relationship('WurkerCron', secondary=wurker_bee_cron, back_populates='bees')
    queue = relationship('WurkerQueue', back_populates='bee')
    reports = relationship('WurkerBeeReport', back_populates='bee', cascade='all, delete', passive_deletes=True)

    __table_args__ = (UniqueConstraint("app_id", "slug"),)


class WurkerCron(Base):
    __tablename__ = "wurker_cron"

    id = Column(Integer, primary_key=True)
    app_id = Column(UUIDType(binary=True), ForeignKey('wurker_control.app_id'), nullable=False)
    slug = Column(String(64), nullable=False)
    name = Column(String(64), nullable=False)
    cron = Column(String(32), nullable=True)
    overrun = Column(Integer, nullable=False, default=0)

    bees = relationship('WurkerBee', secondary=wurker_bee_cron, back_populates='crons')
    reports = relationship('WurkerBeeReport', back_populates='cron', cascade='all, delete', passive_deletes=True)
    control = relationship('WurkerControl')

    __table_args__ = (UniqueConstraint("app_id", "slug"),)


class WurkerQueue(Base):
    __tablename__ = "wurker_queue"

    wurker_bee_id = Column(Integer, ForeignKey('wurker_bee.id'), nullable=False, primary_key=True)
    app_id = Column(UUIDType(binary=True), ForeignKey('wurker_control.app_id'), nullable=False)
    args = Column(JSON)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    bee = relationship('WurkerBee', back_populates='queue')
    control = relationship('WurkerControl')


class WurkerBeeReport(Base):
    __tablename__ = "wurker_bee_report"

    id = Column(Integer, primary_key=True)
    app_id = Column(UUIDType(binary=True), ForeignKey('wurker_control.app_id', ondelete='CASCADE'), nullable=False)
    wurker_bee_id = Column(Integer, ForeignKey('wurker_bee.id', ondelete='CASCADE'), nullable=False)
    wurker_cron_id = Column(Integer, ForeignKey('wurker_cron.id', ondelete='CASCADE'))
    args = Column(JSON)
    hive_pid = Column(Integer, nullable=True)
    hive_timestamp = Column(DateTime(timezone=True), nullable=True)
    hive_runcount = Column(Integer, nullable=True, default=0)
    hive_duration = Column(Integer, nullable=True, default=0)
    status = Column(Enum(WurkerBeeState), nullable=False, default=WurkerBeeState.PROCESSING)
    status_info = Column(JSON)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(
        DateTime(timezone=True),
        server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
        server_onupdate=FetchedValue()
    )

    bee = relationship('WurkerBee', back_populates='reports')
    cron = relationship('WurkerCron', back_populates='reports')
    control = relationship('WurkerControl')


# Map everything out for easy access elsewhere
FILLMAP = {
    "command": {
        "model": WurkerCommand,
        "fillable": {
            "name": str,
            "slug": str,
            "description": str,
            "command": str,
            "is_module": bool,
        },
        "required": {"name", "slug", "command"},
    },
    "bee": {
        "model": WurkerBee,
        "fillable": {
            "name": str,
            "slug": str,
            "description": str,
            "command_id": int,
            "command_slug": str,
            "args": str,
            "quiet_restart": bool,
            "enabled": bool,
        },
        "required": {"name", "slug"},
    },
    "cron": {
        "model": WurkerCron,
        "fillable": {
            "name": str,
            "slug": str,
            "cron": str,
            "overrun": int,
        },
        "required": {"name", "slug"},
    },
}
