from __future__ import annotations

import enum
import uuid as uuid_lib

import sqlalchemy

from .base import Base


class WorkspaceType(enum.StrEnum):
    PERSONAL = 'personal'
    TEAM = 'team'


class WorkspaceStatus(enum.StrEnum):
    PROVISIONING = 'provisioning'
    ACTIVE = 'active'
    SUSPENDED = 'suspended'
    ARCHIVED = 'archived'
    DELETED = 'deleted'


class WorkspaceSource(enum.StrEnum):
    LOCAL = 'local'
    CLOUD_PROJECTION = 'cloud_projection'


class MembershipRole(enum.StrEnum):
    OWNER = 'owner'
    ADMIN = 'admin'
    DEVELOPER = 'developer'
    OPERATOR = 'operator'
    VIEWER = 'viewer'


class MembershipStatus(enum.StrEnum):
    ACTIVE = 'active'
    DISABLED = 'disabled'
    REMOVED = 'removed'


class MembershipSource(enum.StrEnum):
    LOCAL = 'local'
    CLOUD_PROJECTION = 'cloud_projection'


class InvitationStatus(enum.StrEnum):
    PENDING = 'pending'
    ACCEPTED = 'accepted'
    REVOKED = 'revoked'
    EXPIRED = 'expired'


class WorkspaceExecutionStatus(enum.StrEnum):
    PROVISIONING = 'provisioning'
    ACTIVE = 'active'
    MIGRATING = 'migrating'
    DRAINING = 'draining'
    INACTIVE = 'inactive'


class WorkspaceExecutionSource(enum.StrEnum):
    LOCAL = 'local'
    CLOUD = 'cloud'


def _new_uuid() -> str:
    return str(uuid_lib.uuid4())


class Workspace(Base):
    __tablename__ = 'workspaces'

    uuid = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, default=_new_uuid)
    instance_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
    name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
    slug = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
    type = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=WorkspaceType.TEAM.value,
    )
    status = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=WorkspaceStatus.ACTIVE.value,
    )
    created_by_account_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('users.uuid', ondelete='SET NULL'),
        nullable=True,
    )
    source = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=WorkspaceSource.LOCAL.value,
    )
    projection_revision = sqlalchemy.Column(sqlalchemy.BigInteger, nullable=False, server_default='0')
    created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
    updated_at = sqlalchemy.Column(
        sqlalchemy.DateTime,
        nullable=False,
        server_default=sqlalchemy.func.now(),
        onupdate=sqlalchemy.func.now(),
    )

    __table_args__ = (
        sqlalchemy.UniqueConstraint('instance_uuid', 'slug', name='uq_workspaces_instance_slug'),
        sqlalchemy.Index('ix_workspaces_instance_status', 'instance_uuid', 'status'),
        sqlalchemy.Index(
            'uq_workspaces_local_instance',
            'instance_uuid',
            unique=True,
            sqlite_where=sqlalchemy.text("source = 'local'"),
            postgresql_where=sqlalchemy.text("source = 'local'"),
        ),
        sqlalchemy.CheckConstraint(
            "type IN ('personal', 'team')",
            name='ck_workspaces_type',
        ),
        sqlalchemy.CheckConstraint(
            "status IN ('provisioning', 'active', 'suspended', 'archived', 'deleted')",
            name='ck_workspaces_status',
        ),
        sqlalchemy.CheckConstraint(
            "source IN ('local', 'cloud_projection')",
            name='ck_workspaces_source',
        ),
    )


class WorkspaceMembership(Base):
    __tablename__ = 'workspace_memberships'

    uuid = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, default=_new_uuid)
    workspace_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('workspaces.uuid', ondelete='CASCADE'),
        nullable=False,
    )
    account_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('users.uuid', ondelete='CASCADE'),
        nullable=False,
    )
    role = sqlalchemy.Column(sqlalchemy.String(32), nullable=False)
    status = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=MembershipStatus.ACTIVE.value,
    )
    invited_by_account_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('users.uuid', ondelete='SET NULL'),
        nullable=True,
    )
    joined_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)
    source = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=MembershipSource.LOCAL.value,
    )
    projection_revision = sqlalchemy.Column(sqlalchemy.BigInteger, nullable=False, server_default='0')
    created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
    updated_at = sqlalchemy.Column(
        sqlalchemy.DateTime,
        nullable=False,
        server_default=sqlalchemy.func.now(),
        onupdate=sqlalchemy.func.now(),
    )

    __table_args__ = (
        sqlalchemy.UniqueConstraint('workspace_uuid', 'account_uuid', name='uq_workspace_membership_account'),
        sqlalchemy.Index('ix_workspace_memberships_account_status', 'account_uuid', 'status'),
        sqlalchemy.Index(
            'uq_workspace_memberships_one_active_owner',
            'workspace_uuid',
            unique=True,
            sqlite_where=sqlalchemy.text("role = 'owner' AND status = 'active'"),
            postgresql_where=sqlalchemy.text("role = 'owner' AND status = 'active'"),
        ),
        sqlalchemy.CheckConstraint(
            "role IN ('owner', 'admin', 'developer', 'operator', 'viewer')",
            name='ck_workspace_memberships_role',
        ),
        sqlalchemy.CheckConstraint(
            "status IN ('active', 'disabled', 'removed')",
            name='ck_workspace_memberships_status',
        ),
        sqlalchemy.CheckConstraint(
            "source IN ('local', 'cloud_projection')",
            name='ck_workspace_memberships_source',
        ),
    )


class WorkspaceInvitation(Base):
    __tablename__ = 'workspace_invitations'

    uuid = sqlalchemy.Column(sqlalchemy.String(36), primary_key=True, default=_new_uuid)
    workspace_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('workspaces.uuid', ondelete='CASCADE'),
        nullable=False,
    )
    normalized_email = sqlalchemy.Column(sqlalchemy.String(320), nullable=False)
    role = sqlalchemy.Column(sqlalchemy.String(32), nullable=False)
    token_hash = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
    status = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=InvitationStatus.PENDING.value,
    )
    expires_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False)
    accepted_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)
    revoked_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)
    created_by_account_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('users.uuid', ondelete='CASCADE'),
        nullable=False,
    )
    created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
    updated_at = sqlalchemy.Column(
        sqlalchemy.DateTime,
        nullable=False,
        server_default=sqlalchemy.func.now(),
        onupdate=sqlalchemy.func.now(),
    )

    __table_args__ = (
        sqlalchemy.Index('uq_workspace_invitations_token_hash', 'token_hash', unique=True),
        sqlalchemy.Index(
            'uq_workspace_invitations_pending_email',
            'workspace_uuid',
            'normalized_email',
            unique=True,
            sqlite_where=sqlalchemy.text("status = 'pending'"),
            postgresql_where=sqlalchemy.text("status = 'pending'"),
        ),
        sqlalchemy.CheckConstraint(
            "role IN ('admin', 'developer', 'operator', 'viewer')",
            name='ck_workspace_invitations_role',
        ),
        sqlalchemy.CheckConstraint(
            "status IN ('pending', 'accepted', 'revoked', 'expired')",
            name='ck_workspace_invitations_status',
        ),
    )


class WorkspaceExecutionState(Base):
    __tablename__ = 'workspace_execution_states'

    workspace_uuid = sqlalchemy.Column(
        sqlalchemy.String(36),
        sqlalchemy.ForeignKey('workspaces.uuid', ondelete='CASCADE'),
        primary_key=True,
    )
    instance_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
    active_generation = sqlalchemy.Column(sqlalchemy.BigInteger, nullable=False, server_default='1')
    state = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=WorkspaceExecutionStatus.ACTIVE.value,
    )
    write_fenced = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, server_default=sqlalchemy.false())
    source = sqlalchemy.Column(
        sqlalchemy.String(32),
        nullable=False,
        server_default=WorkspaceExecutionSource.LOCAL.value,
    )
    desired_state_revision = sqlalchemy.Column(sqlalchemy.BigInteger, nullable=False, server_default='0')
    updated_at = sqlalchemy.Column(
        sqlalchemy.DateTime,
        nullable=False,
        server_default=sqlalchemy.func.now(),
        onupdate=sqlalchemy.func.now(),
    )

    __table_args__ = (
        sqlalchemy.Index('ix_workspace_execution_states_instance_state', 'instance_uuid', 'state'),
        sqlalchemy.CheckConstraint('active_generation > 0', name='ck_workspace_execution_generation'),
        sqlalchemy.CheckConstraint(
            "state IN ('provisioning', 'active', 'migrating', 'draining', 'inactive')",
            name='ck_workspace_execution_state',
        ),
        sqlalchemy.CheckConstraint(
            "source IN ('local', 'cloud')",
            name='ck_workspace_execution_source',
        ),
    )
