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.usertype import UserType
from cassandra.cqlengine import columns
from cassandra.util import uuid_from_time, unix_time_from_uuid1
from cassandra.cqlengine.columns import UserDefinedType


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 WebRunComment(UserType):
    user_id = columns.UUID(required=True)
    timestamp = columns.Integer(required=True)
    message = columns.Text(min_length=1)
    mentions = columns.List(value_type=columns.UUID, default=[])

    def to_json(self):
        return {
            'user_id': self.user_id,
            'timestamp': self.timestamp,
            'message': self.message,
            'mentions': self.mentions,
        }


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)

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

    def is_manager(self) -> bool:
        return UserRoles.Manager in self.roles

    def is_admin(self) -> bool:
        return UserRoles.Admin in self.roles

    def set_as_admin(self) -> None:
        if UserRoles.Admin not in self.roles:
            self.roles.append(UserRoles.Admin.value)

    def set_as_manager(self) -> None:
        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:
            return None

    @classmethod
    def exists_by_name(cls, name: str):
        try:
            user = cls.get(username=name)
            if user:
                return user
        except cls.DoesNotExist:
            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 ArgusReleaseGroup(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, ArgusReleaseGroup):
            return self.name == other.name and self.release_id == other.release_id
        else:
            return super().__eq__(other)


class ArgusReleaseGroupTest(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, ArgusReleaseGroupTest):
            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 = ArgusReleaseGroupTest.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 ArgusReleaseGroupTest.DoesNotExist:
            pass
        except ArgusReleaseGroupTest.MultipleObjectsReturned:
            raise

        return


class ArgusPlannedTestsForRelease(Model):
    id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
    release_id = columns.UUID(required=True, index=True)
    group_id = columns.UUID(required=True, index=True)
    test_id = columns.UUID(required=True, index=True)


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 ArgusReleaseSchedule(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 ArgusReleaseScheduleAssignee(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 ArgusReleaseScheduleTest(Model):
    __table_name__ = "argus_schedule_test_v3"
    test_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 ArgusReleaseScheduleGroup(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 WebRunComments(Model):
    test_id = columns.UUID(primary_key=True, default=uuid4)
    comments = columns.List(
        value_type=UserDefinedType(WebRunComment), default=list)

    def get_comments_by_user(self, user: User):
        return [(idx, comment) for idx, comment in enumerate(self.comments) if comment.user_id == user.id]

    def to_json(self):
        return {
            'test': self.test_id,
            'comments': [comment.to_json() for comment in self.comments]  # pylint: disable=not-an-iterable
        }


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


class WebRelease(Model):
    id = columns.UUID(primary_key=True, default=uuid4)
    name = columns.Text(min_length=1)
    pretty_name = columns.Text()
    description = columns.Text()
    picture_id = columns.UUID()


class WebCategoryGroup(Model):
    id = columns.UUID(primary_key=True, default=uuid4)
    name = columns.Text(min_length=1)
    pretty_name = columns.Text()
    description = columns.Text()


class WebNemesis(Model):
    id = columns.UUID(primary_key=True, default=uuid4)
    name = columns.Text(min_length=1)
    pretty_name = columns.Text()
    description = columns.Text()


USED_MODELS = [
    User, WebRunComments, WebRelease, WebCategoryGroup, WebNemesis,
    ArgusRelease, ArgusReleaseGroup, ArgusReleaseGroupTest, ArgusPlannedTestsForRelease,
    ArgusTestRunComment, ArgusEvent, UserOauthToken,
    WebFileStorage, ArgusGithubIssue, ReleasePlannerComment, ArgusNotification,
    ArgusReleaseSchedule, ArgusReleaseScheduleAssignee, ArgusReleaseScheduleGroup, ArgusReleaseScheduleTest
]
USED_TYPES = [WebRunComment]
