from uuid import UUID, uuid1, uuid4
from datetime import datetime
from enum import Enum, IntEnum, auto
from cassandra.cqlengine.models import Model
from cassandra.cqlengine import columns
from cassandra.util import uuid_from_time, unix_time_from_uuid1  # pylint: disable=no-name-in-module


def uuid_now():
    return uuid_from_time(datetime.utcnow())

# pylint: disable=invalid-name


class ArgusTestException(Exception):
    pass


class UserRoles(str, Enum):
    User = "ROLE_USER"
    Manager = "ROLE_MANAGER"
    Admin = "ROLE_ADMIN"


class User(Model):
    id = columns.UUID(primary_key=True, default=uuid4)
    username = columns.Text(index=True)
    full_name = columns.Text()
    password = columns.Text()
    email = columns.Text(index=True)
    registration_date = columns.DateTime()
    roles = columns.List(value_type=columns.Text)
    picture_id = columns.UUID(default=None)
    api_token = columns.Text(index=True)

    def __hash__(self) -> int:
        return hash(self.id)

    def is_manager(self) -> bool:
        # pylint: disable=unsupported-membership-test
        return UserRoles.Manager in self.roles

    def is_admin(self) -> bool:
        # pylint: disable=unsupported-membership-test
        return UserRoles.Admin in self.roles

    def set_as_admin(self) -> None:
        # pylint: disable=unsupported-membership-test
        if UserRoles.Admin not in self.roles:
            self.roles.append(UserRoles.Admin.value)

    def set_as_manager(self) -> None:
        # pylint: disable=unsupported-membership-test
        if UserRoles.Manager not in self.roles:
            self.roles.append(UserRoles.Manager.value)

    def get_id(self):
        return str(self.id)

    @classmethod
    def exists(cls, user_id: UUID):
        try:
            user = cls.get(id=user_id)
            if user:
                return user
        except cls.DoesNotExist:
            pass
        return None

    @classmethod
    def exists_by_name(cls, name: str):
        try:
            user = cls.get(username=name)
            if user:
                return user
        except cls.DoesNotExist:
            pass
        return None

    def __str__(self):
        return f"User('{self.id}','{self.username}')"

    def to_json(self):
        return {
            "id": str(self.id),
            "username": self.username,
            "full_name": self.full_name,
            "picture_id": self.picture_id
        }


class UserOauthToken(Model):
    id = columns.UUID(primary_key=True, default=uuid4)
    user_id = columns.UUID(index=True, required=True)
    kind = columns.Text(required=True, index=True)
    token = columns.Text(required=True)


class ArgusRelease(Model):
    __table_name__ = "argus_release_v2"
    id = columns.UUID(primary_key=True, default=uuid4)
    name = columns.Text(index=True, required=True)
    pretty_name = columns.Text()
    description = columns.Text()
    github_repo_url = columns.Text()
    assignee = columns.List(value_type=columns.UUID)
    picture_id = columns.UUID()
    enabled = columns.Boolean(default=lambda: True)
    perpetual = columns.Boolean(default=lambda: True)
    dormant = columns.Boolean(default=lambda: False)

    def __eq__(self, other):
        if isinstance(other, ArgusRelease):
            return self.name == other.name
        else:
            return super().__eq__(other)


class ArgusGroup(Model):
    __table_name__ = "argus_group_v2"
    id = columns.UUID(primary_key=True, default=uuid4)
    release_id = columns.UUID(required=True, index=True)
    name = columns.Text(required=True, index=True)
    pretty_name = columns.Text()
    description = columns.Text()
    assignee = columns.List(value_type=columns.UUID)
    build_system_id = columns.Text()
    enabled = columns.Boolean(default=lambda: True)

    def __hash__(self) -> int:
        return hash((self.id, self.release_id))

    def __eq__(self, other):
        if isinstance(other, ArgusGroup):
            return self.name == other.name and self.release_id == other.release_id
        else:
            return super().__eq__(other)


class ArgusTest(Model):
    __table_name__ = "argus_test_v2"
    id = columns.UUID(primary_key=True, default=uuid4)
    group_id = columns.UUID(required=True, index=True)
    release_id = columns.UUID(required=True, index=True)
    name = columns.Text(required=True, index=True)
    pretty_name = columns.Text()
    description = columns.Text()
    assignee = columns.List(value_type=columns.UUID)
    build_system_id = columns.Text(index=True)
    enabled = columns.Boolean(default=lambda: True)
    build_system_url = columns.Text()

    def __eq__(self, other):
        if isinstance(other, ArgusTest):
            return self.name == other.name and self.group_id == other.group_id and self.release_id == other.release_id
        else:
            return super().__eq__(other)

    def validate_build_system_id(self):
        try:
            t = ArgusTest.get(build_system_id=self.build_system_id)
            if t.id != self.id:
                raise ArgusTestException("Build Id is already used by another test", t.id, self.id)
        except ArgusTest.DoesNotExist:
            pass


class ArgusTestRunComment(Model):
    id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
    test_run_id = columns.UUID(required=True, index=True)
    user_id = columns.UUID(required=True, index=True)
    release_id = columns.UUID(required=True, index=True)
    posted_at = columns.Integer(
        required=True, clustering_order="desc", primary_key=True)
    message = columns.Text(min_length=1, max_length=65535)
    mentions = columns.List(value_type=columns.UUID, default=[])
    reactions = columns.Map(key_type=columns.Text, value_type=columns.Integer)


class ArgusEventTypes(str, Enum):
    AssigneeChanged = "ARGUS_ASSIGNEE_CHANGE"
    TestRunStatusChanged = "ARGUS_TEST_RUN_STATUS_CHANGE"
    TestRunInvestigationStatusChanged = "ARGUS_TEST_RUN_INVESTIGATION_STATUS_CHANGE"
    TestRunCommentPosted = "ARGUS_TEST_RUN_COMMENT_POSTED"
    TestRunCommentUpdated = "ARGUS_TEST_RUN_COMMENT_UPDATED"
    TestRunCommentDeleted = "ARGUS_TEST_RUN_COMMENT_DELETED"
    TestRunIssueAdded = "ARGUS_TEST_RUN_ISSUE_ADDED"
    TestRunIssueRemoved = "ARGUS_TEST_RUN_ISSUE_REMOVED"


class ArgusEvent(Model):
    id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
    release_id = columns.UUID(index=True)
    group_id = columns.UUID(index=True)
    test_id = columns.UUID(index=True)
    run_id = columns.UUID(index=True)
    user_id = columns.UUID(index=True)
    kind = columns.Text(required=True, index=True)
    body = columns.Text(required=True)
    created_at = columns.DateTime(required=True)


class ArgusNotificationTypes(str, Enum):
    Mention = "TYPE_MENTION"
    StatusChange = "TYPE_STATUS_CHANGE"
    AssigneeChange = "TYPE_ASSIGNEE_CHANGE"
    ScheduleChange = "TYPE_SCHEDULE_CHANGE"


class ArgusNotificationSourceTypes(str, Enum):
    TestRun = "TEST_RUN"
    Schedule = "SCHEDULE"
    Comment = "COMMENT"


class ArgusNotificationState(IntEnum):
    UNREAD = auto()
    READ = auto()


class ArgusNotification(Model):
    receiver = columns.UUID(primary_key=True, partition_key=True)
    id = columns.TimeUUID(primary_key=True, clustering_order="DESC", default=uuid_now)
    type = columns.Text(required=True)
    state = columns.SmallInt(required=True, default=lambda: ArgusNotificationState.UNREAD)
    sender = columns.UUID(required=True)
    source_type = columns.Text(required=True)
    source_id = columns.UUID(required=True)
    title = columns.Text(required=True, max_length=1024)
    content = columns.Text(required=True, max_length=65535)

    def to_dict_short_summary(self) -> dict:
        return {
            "receiver": self.receiver,
            "sender": self.sender,
            "id": self.id,
            "created": unix_time_from_uuid1(self.id) * 1000,
            "title": self.title,
            "state": self.state,
        }

    def to_dict(self) -> dict:
        return {
            "receiver": self.receiver,
            "sender": self.sender,
            "id": self.id,
            "created": unix_time_from_uuid1(self.id) * 1000,
            "title": self.title,
            "type": self.type,
            "content": self.content,
            "source": self.source_type,
            "source_id": self.source_id,
            "state": self.state,
        }


class ArgusGithubIssue(Model):
    # pylint: disable=too-many-instance-attributes
    id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
    added_on = columns.DateTime(default=datetime.utcnow)
    release_id = columns.UUID(index=True)
    group_id = columns.UUID(index=True)
    test_id = columns.UUID(index=True)
    run_id = columns.UUID(index=True)
    user_id = columns.UUID(index=True)
    type = columns.Text()
    owner = columns.Text()
    repo = columns.Text()
    issue_number = columns.Integer()
    last_status = columns.Text()
    title = columns.Text()
    url = columns.Text()

    def __hash__(self) -> int:
        return hash((self.owner, self.repo, self.issue_number))

    def __eq__(self, other):
        if isinstance(other, ArgusGithubIssue):
            return self.owner == other.owner and self.repo == other.repo and self.issue_number == other.issue_number
        return super().__eq__(other)

    def __ne__(self, other):
        return not self == other


class ArgusSchedule(Model):
    __table_name__ = "argus_schedule_v4"
    release_id = columns.UUID(primary_key=True, required=True)
    id = columns.TimeUUID(primary_key=True, default=uuid1, clustering_order="DESC")
    period_start = columns.DateTime(required=True, default=datetime.utcnow)
    period_end = columns.DateTime(required=True, primary_key=True, clustering_order="DESC")
    tag = columns.Text(default="")


class ArgusScheduleAssignee(Model):
    __table_name__ = "argus_schedule_user_v3"
    assignee = columns.UUID(primary_key=True)
    id = columns.TimeUUID(primary_key=True, default=uuid1,
                          clustering_order="DESC")
    schedule_id = columns.TimeUUID(required=True, index=True)
    release_id = columns.UUID(required=True)


class ArgusScheduleTest(Model):
    __table_name__ = "argus_schedule_test_v5"
    test_id = columns.UUID(primary_key=True, required=True)
    id = columns.TimeUUID(primary_key=True, default=uuid1,
                          clustering_order="DESC")
    schedule_id = columns.TimeUUID(required=True, index=True)
    release_id = columns.UUID(partition_key=True)


class ArgusScheduleGroup(Model):
    __table_name__ = "argus_schedule_group_v3"
    group_id = columns.UUID(partition_key=True, required=True)
    id = columns.TimeUUID(primary_key=True, default=uuid1,
                          clustering_order="DESC")
    schedule_id = columns.TimeUUID(required=True, index=True)
    release_id = columns.UUID(partition_key=True)


class ReleasePlannerComment(Model):
    __table_name__ = "argus_planner_comment_v2"
    release = columns.UUID(primary_key=True)
    group = columns.UUID(primary_key=True)
    test = columns.UUID(primary_key=True)
    comment = columns.Text(default=lambda: "")


class WebFileStorage(Model):
    id = columns.UUID(primary_key=True, default=uuid4)
    filepath = columns.Text(min_length=1)
    filename = columns.Text(min_length=1)


USED_MODELS = [
    User, UserOauthToken,
    ArgusRelease, ArgusGroup, ArgusTest,
    ArgusTestRunComment, ArgusEvent,
    WebFileStorage, ArgusGithubIssue, ReleasePlannerComment, ArgusNotification,
    ArgusSchedule, ArgusScheduleAssignee, ArgusScheduleGroup, ArgusScheduleTest
]
USED_TYPES = []
