from datetime import datetime

from asgiref.sync import sync_to_async
from django.core.validators import *
from django.db import models

from hashlib import sha512, sha256

from django.db.models import Q

from arka.settings import CITIES_CHOICES, CITIES_FIELD_SIZE
from .api_errors import *
import re


class Account(models.Model):
    surname = models.CharField(max_length=60, verbose_name="Фамилия", blank=True, default="")
    name = models.CharField(max_length=60, verbose_name="Имя", blank=True, default="")
    phone = models.CharField(unique=True, max_length=16, verbose_name="Телефон", validators=[
        RegexValidator(regex="^\\+7[0-9]{10}$"),
    ])
    email = models.EmailField(unique=True, verbose_name="Почта", blank=True, null=True, default=None)

    password = models.CharField(max_length=64, blank=True, verbose_name="Хеш пароля")

    # роль
    ROLE_CUSTOMER, ROLE_EXECUTOR, ROLE_MODER, ROLE_ADMIN = range(4)

    ROLE_CHOICES = [
        (ROLE_CUSTOMER, 'Заказчик'),
        (ROLE_EXECUTOR, 'Исполнитель'),
        # TODO добавить модератора и админа в API
        # (ROLE_MODER, 'Модератор'),
        # (ROLE_ADMIN, 'Админ')
    ]
    role = models.SmallIntegerField(choices=ROLE_CHOICES, verbose_name="Роль", editable=False)

    about = models.CharField(max_length=1000, blank=True, default="", verbose_name="О себе")

    city = models.CharField(max_length=CITIES_FIELD_SIZE, choices=CITIES_CHOICES, default=None, null=True, blank=True)

    register_datetime = models.DateTimeField(default=datetime.now, editable=False)

    verified = models.BooleanField(default=False, verbose_name="Подтвержденный аккаунт")

    @staticmethod
    def create_user(phone: str, **kvargs):
        return Account(phone=Account.normalize_phone(phone), **kvargs)

    @staticmethod
    def normalize_phone(phone):
        # Гоша попросил запилить фичу, чтобы принимались номера:
        #   +79991112233
        #   79991112233
        #   9991112233
        if re.match("^[0-9]{10}$", phone) is not None:
            return f"+7{phone}"
        elif re.match("^7[0-9]{10}$", phone) is not None:
            return f"+{phone}"
        else:
            return phone

    @staticmethod
    async def get_by_natural_key(key):
        try:
            return await Account.objects.aget(phone=Account.normalize_phone(key))
        except:
            raise Exception(API_ERROR_NOT_FOUND, "user")

    async def get_by_id(self, user_id: int):
        u = Account.objects.filter(id=user_id).select_related('executoraccount', 'accountavatar')
        if self.role == Account.ROLE_EXECUTOR or self.role == Account.ROLE_CUSTOMER:
            u.filter(role__in=[Account.ROLE_EXECUTOR, Account.ROLE_CUSTOMER])

        return await u.afirst()

    def save(self, **kvargs):
        if len(self.password) != 64:
            self.password = sha256(self.password.encode('utf-8')).hexdigest()

        if self.role != Account.ROLE_EXECUTOR and hasattr(self, 'executoraccount'):
            self.executoraccount.delete()

        super(Account, self).save(**kvargs)

    def check_password(self, password):
        return sha256(password.encode('utf-8')).hexdigest() == self.password

    def __str__(self):
        r = None if self.role is None else ExecutorAccount.EXECUTOR_TYPE_CHOICES[self.role][1]
        return f"{self.name} {self.surname}: {self.phone} ({r})"

    def is_completed(self):
        return self.name != '' and self.surname != '' and self.city is not None


class Media(models.Model):
    owner = models.ForeignKey(Account, on_delete=models.SET_NULL, null=True,
                              related_name="media_owner", verbose_name="Владелец")
    storage_name = models.CharField(max_length=80, verbose_name="Файл в хранилище")
    original_name = models.CharField(max_length=100, verbose_name="Имя файла", default="")
    extension = models.CharField(max_length=16)
    size = models.IntegerField(verbose_name='Размер в байтах')
    upload_datetime = models.DateTimeField(default=datetime.now, editable=False)

    PHOTO_EXTENSIONS = ['jpg', 'jpeg', 'png']

    @staticmethod
    async def get_by_id(user_id: int, media_id: int):
        m = Media.objects.filter(user_id=user_id, id=media_id).select_related('executoraccount', 'city')
        return await m.afirst()

    @staticmethod
    async def get_media():
        pass

    @staticmethod
    def generate_storage_name(original_name, upload_datetime, user_id):
        source_str = f"{original_name} {upload_datetime} {user_id}"
        return sha256(bytearray(source_str, 'utf-8')).hexdigest()

    def __str__(self):
        return f"{self.owner}: \"{self.original_name}\" ({self.id})"


class AccountAvatar(models.Model):
    account = models.OneToOneField(Account, on_delete=models.CASCADE, verbose_name="Аккаунт")
    photo = models.ForeignKey(Media, on_delete=models.SET_NULL, null=True, blank=True, default=None,
                              related_name="photo", verbose_name="Аватар")
    profile_background = models.ForeignKey(Media, on_delete=models.SET_NULL, null=True, blank=True, default=None,
                                           related_name="profile_background", verbose_name="Оформление профиля")


def _executor_additional_info_default():
    return {}


class ExecutorAccount(models.Model):
    account = models.OneToOneField(Account, on_delete=models.CASCADE, primary_key=True)

    # тип исполнителя
    EXECUTOR_TYPE_SELF_EMPLOYED, EXECUTOR_TYPE_LEGAL_ENTITY = range(2)
    EXECUTOR_TYPE_CHOICES = [
        (EXECUTOR_TYPE_SELF_EMPLOYED, "самозанятый"),
        (EXECUTOR_TYPE_LEGAL_ENTITY, "юр. лицо"),
    ]
    executor_type = models.SmallIntegerField(choices=EXECUTOR_TYPE_CHOICES, verbose_name="Тип исполнителя",
                                             blank=True, null=True)

    # ИНН
    inn = models.CharField(max_length=12, null=True, blank=True, validators=[
        RegexValidator(regex="^[0-9]{10}$|^[0-9]{12}$"),
    ], verbose_name="ИНН")

    # дополнительная информация
    additional_info = models.JSONField(default=_executor_additional_info_default, null=True, blank=True,
                                       verbose_name="Дополнительные данные")

    def __str__(self):
        e_t = None if self.executor_type is None else ExecutorAccount.EXECUTOR_TYPE_CHOICES[self.executor_type][1]
        return f"{self.account}: {e_t}"


class AccessToken(models.Model):
    user = models.ForeignKey(Account, on_delete=models.CASCADE)
    access_token = models.CharField(max_length=128, editable=False, unique=True)
    creation_time = models.DateTimeField(default=datetime.now)

    @staticmethod
    async def create_token(user: Account):
        token = AccessToken(user=user, access_token=AccessToken._generate_token(user))
        print(f"created token {token.access_token[:16]}...")
        await sync_to_async(token.save)()
        return token

    @staticmethod
    async def auth(login: str, password: str):
        try:
            user = await Account.get_by_natural_key(login)
        except Exception:
            raise Exception(API_ERROR_INVALID_LOGIN)

        if not user.check_password(password):
            raise Exception(API_ERROR_INVALID_PASSWORD)

        return await AccessToken.create_token(user)

    @staticmethod
    async def deauth(token: str):
        t = await AccessToken.get_by_token(token)
        await sync_to_async(t.delete)()

    @staticmethod
    async def get_user_by_token(token: str):
        return (await AccessToken.get_by_token(token)).owner

    @staticmethod
    async def get_by_token(token: str):
        related = [
            'user',
            'user__accountavatar',
            'user__accountavatar__profile_background',
            'user__accountavatar__photo',
            'user__executoraccount'
        ]
        t = await AccessToken.objects.filter(access_token=token).select_related(*related).afirst()
        if t is None:
            raise Exception(API_ERROR_INVALID_TOKEN)
        return t

    def __str__(self):
        return f"{self.user.name} {self.user.surname} ({self.user.phone}): {self.access_token[:8]}..."

    @staticmethod
    def _generate_token(user: Account):
        return sha512(bytearray(user.phone + user.password + str(datetime.now()), 'utf-8')).hexdigest()

    def save(self, *args, **kwargs):
        if self.access_token is None or len(self.access_token) == 0:
            self.access_token = AccessToken._generate_token(self.user)

        super().save(*args, **kwargs)

    async def list_sessions(self):
        sessions = AccessToken.objects.filter(~Q(access_token=self.access_token), user=self.user)
        return [item async for item in sessions]

    async def delete_sessions_without_current(self):
        await sync_to_async(AccessToken.objects.filter(~Q(access_token=self.access_token), user=self.user).delete)()

    async def delete_session(self, session_id: int):
        session = await AccessToken.objects.filter(~Q(access_token=self.access_token),
                                                   user=self.user, id=session_id).afirst()
        if session is None:
            raise Exception(API_ERROR_NOT_FOUND, "session")

        await sync_to_async(session.delete)()

    @staticmethod
    async def delete_sessions(user: Account):
        await sync_to_async(AccessToken.objects.filter(user=user).delete)()


class Order(models.Model):
    owner = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="owner", verbose_name="Владелец")

    # основные поля: название и описание
    name = models.CharField(max_length=200, verbose_name="Название объявления")
    address_city = models.CharField(max_length=CITIES_FIELD_SIZE, choices=CITIES_CHOICES,
                                    blank=False, verbose_name="Город")
    address_text = models.CharField(max_length=70, blank=True, verbose_name="Улица, дом")

    CHOICE_UNDEFINED = ''

    # Раздел "Параметры объекта"

    # тип квартиры
    TYPE_OF_APARTMENT_PRIMARY = 'primary'
    TYPE_OF_APARTMENT_SECONDARY = 'secondary'

    TYPE_OF_APARTMENT_CHOICES = [
        (TYPE_OF_APARTMENT_PRIMARY, 'Первичка'),
        (TYPE_OF_APARTMENT_SECONDARY, 'Вторичка')
    ]
    type_of_apartment = models.CharField(max_length=10, choices=TYPE_OF_APARTMENT_CHOICES, blank=True,
                                         default=CHOICE_UNDEFINED, verbose_name="Вид объекта")

    # тип дома
    TYPE_OF_HOUSE_BLOCK = 'block'
    TYPE_OF_HOUSE_BRICK = 'brick'
    TYPE_OF_HOUSE_MONOLITH = 'monolith'
    TYPE_OF_HOUSE_PANEL = 'panel'

    # TODO добавить чойсов, их на самом деле больше
    TYPE_OF_HOUSE_CHOICES = [
        (TYPE_OF_HOUSE_BLOCK, 'Блочный'),
        (TYPE_OF_HOUSE_BRICK, 'Кирпичный'),
        (TYPE_OF_HOUSE_MONOLITH, 'Монолит'),
        (TYPE_OF_HOUSE_PANEL, 'Панельный'),
    ]
    type_of_house = models.CharField(max_length=10, choices=TYPE_OF_HOUSE_CHOICES, blank=True, default=CHOICE_UNDEFINED,
                                     verbose_name="Тип дома")

    TYPE_OF_ROOM_APARTMENT = "apartment"
    TYPE_OF_ROOM_PRIVATE_HOUSE = "private_house"
    TYPE_OF_ROOM_PENTHOUSE = "penthouse"
    TYPE_OF_ROOM_COUNTRY_HOUSE = "country_house"
    TYPE_OF_ROOM_APARTMENTS = "apartments"
    TYPE_OF_ROOM_KITCHEN = "kitchen"
    TYPE_OF_ROOM_BATHROOM = "bathroom"
    TYPE_OF_ROOM_NURSERY = "nursery"

    TYPE_OF_ROOM_CHOICES = [
        (TYPE_OF_ROOM_APARTMENT, "квартира"),
        (TYPE_OF_ROOM_PRIVATE_HOUSE, "частный дом"),
        (TYPE_OF_ROOM_PENTHOUSE, "пентхаус"),
        (TYPE_OF_ROOM_COUNTRY_HOUSE, "дачный дом"),
        (TYPE_OF_ROOM_APARTMENTS, "апартаменты"),
        (TYPE_OF_ROOM_KITCHEN, "кухня"),
        (TYPE_OF_ROOM_BATHROOM, "ванная комната"),
        (TYPE_OF_ROOM_NURSERY, "детская")
    ]
    type_of_room = models.CharField(max_length=16, choices=TYPE_OF_ROOM_CHOICES, blank=True,
                                    default=CHOICE_UNDEFINED, verbose_name="Тип помещения")

    number_of_rooms = models.SmallIntegerField(verbose_name='Количество комнат, -1 = студия', validators=[
        MinValueValidator(-1),
        MaxValueValidator(100)
    ])

    is_balcony = models.BooleanField(default=False, verbose_name="Балкон")
    is_loggia = models.BooleanField(default=False, verbose_name="Лоджия")

    state_of_room = models.CharField(max_length=40, blank=True, default=CHOICE_UNDEFINED,
                                     verbose_name="Состояние помещения")

    # площадь в квадратных метрах
    square = models.DecimalField(max_digits=7, decimal_places=2, blank=False, verbose_name="Площадь в м²")

    # высота потолков
    ceiling_height = models.DecimalField(max_digits=4, decimal_places=2, blank=False,
                                         verbose_name="Высота потолков в м")

    # Раздел "Ремонт"

    # тип ремонта
    TYPE_OF_RENOVATION_OVERHAUL = 'overhaul'
    TYPE_OF_RENOVATION_REDECOR = 'redecor'
    TYPE_OF_RENOVATION_PARTIAL = 'partial'

    TYPE_OF_RENOVATION_CHOICES = [
        (CHOICE_UNDEFINED, 'Не определено'),
        (TYPE_OF_RENOVATION_OVERHAUL, 'Капитальный'),
        (TYPE_OF_RENOVATION_REDECOR, 'Косметический'),
        (TYPE_OF_RENOVATION_PARTIAL, 'Частичный'),
    ]
    type_of_renovation = models.CharField(max_length=10, choices=TYPE_OF_RENOVATION_CHOICES, default=CHOICE_UNDEFINED,
                                          blank=True, verbose_name="Тип ремонта")

    is_redevelopment = models.BooleanField(default=False, verbose_name="Перепланировка")
    is_leveling_floors = models.BooleanField(default=False, verbose_name="Выравнивать полы")
    is_heated_floor = models.BooleanField(default=False, verbose_name="Теплый пол")
    is_leveling_walls = models.BooleanField(default=False, verbose_name="Выравнивать стены")

    type_of_ceiling = models.CharField(max_length=40, blank=True, default=CHOICE_UNDEFINED,
                                       verbose_name="Тип потолка")

    is_wiring_replace = models.BooleanField(default=False, verbose_name="Требуется замена проводки")
    is_require_design = models.BooleanField(default=False, verbose_name="Требуется дизайн проект")

    # закуп материала
    PURCHASE_OF_MATERIAL_EXECUTOR = 'executor'
    PURCHASE_OF_MATERIAL_CUSTOMER = 'customer'

    PURCHASE_OF_MATERIAL_CHOICES = [
        (PURCHASE_OF_MATERIAL_EXECUTOR, 'Исполнитель'),
        (PURCHASE_OF_MATERIAL_CUSTOMER, 'Заказчик')
    ]
    purchase_of_material = models.CharField(max_length=10, choices=PURCHASE_OF_MATERIAL_CHOICES,
                                            blank=True, default=CHOICE_UNDEFINED, verbose_name="Закуп материала")

    # дальше отдельные параметры
    is_with_contract = models.BooleanField(default=False, verbose_name="Работа по договору")
    is_with_warranty = models.BooleanField(default=True, verbose_name="С гарантией")
    is_with_trade = models.BooleanField(default=False, verbose_name="Возможен торг")
    is_with_garbage_removal = models.BooleanField(default=False, verbose_name="С вывозом мусора")

    date_start = models.DateField(null=True, blank=True, default=None, verbose_name="Дата начала")
    date_end = models.DateField(null=True, blank=True, default=None, verbose_name="Дата окончания")

    # примерная цена
    approximate_price = models.DecimalField(max_digits=12, decimal_places=2, blank=False, verbose_name="Цена")

    description = models.TextField(blank=True, verbose_name="Описание")

    video_link = models.CharField(max_length=160, blank=True, default=CHOICE_UNDEFINED,
                                  verbose_name="Ссылка на видео")

    email = models.EmailField(blank=True, verbose_name="Email")
    phone = models.CharField(blank=True, max_length=16, verbose_name="Телефон", validators=[
        RegexValidator(regex="^\\+7[0-9]{10}$")
    ])

    # Внутренние поля

    create_time = models.DateTimeField(default=datetime.now, editable=False, verbose_name="Время создания")

    moderated = models.BooleanField(default=True, verbose_name="Модерирован")
    published = models.BooleanField(default=False, verbose_name="Опубликован")

    def __str__(self):
        return self.name

    @staticmethod
    def get_all_for_user(user):
        if user.is_staff:
            return Order.objects.filter().order_by('create_time')
        else:
            return Order.objects.filter(published=True, moderated=True).order_by('create_time')

    @staticmethod
    def get_for_user_by_id(user, order_id):
        q = Order.get_all_for_user(user).filter(id=order_id)
        if len(q) == 0:
            return None
        else:
            return q[0]


def _portfolio_default_attrs():
    return {"tags": []}


class Portfolio(models.Model):
    account = models.ForeignKey(Account, on_delete=models.CASCADE, verbose_name="Аккаунт")

    title = models.CharField(max_length=200, verbose_name="Название")
    actual_date = models.DateField(verbose_name="Дата выполнения", default=datetime.now)
    publish_date = models.DateField(verbose_name="Дата публикации", default=datetime.now, editable=False)
    actual_price = models.DecimalField(max_digits=12, decimal_places=2, blank=False, verbose_name="Цена")
    square = models.DecimalField(max_digits=7, decimal_places=2, blank=False, verbose_name="Площадь в м²")

    TAGS_NAMES = [
        ("housings", "Квартиры"),
        ("private_houses", "Частные дома"),
        ("country_houses", "Дачные дома"),
        ("penthouses", "Пентхаусы"),
        ("apartments", "Апартаменты"),
        ("rooms", "Комнаты"),
        ("kitchens", "Кухни"),
        ("bathrooms", "Ванные комнаты"),
        ("child_rooms", "Детские комнаты")
    ]
    attributes = models.JSONField(verbose_name="Атрибуты", default=_portfolio_default_attrs)

    def __str__(self):
        return f"{self.id}: \"{self.title}\""


class PortfolioPhoto(models.Model):
    portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, verbose_name="Портфолио")
    photo = models.ForeignKey(Media, on_delete=models.CASCADE, verbose_name="Фотография")
    is_preview = models.BooleanField(verbose_name="Это главная фотография")

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['portfolio', 'photo'], name='unique_portfoliophoto_photo'
            )
        ]

    # TODO добавить проверку того, чтобы нельзя было приложить медиа другого юзера


# def _upload_image_filename(instance, filename):
#     name, ext = os.path.splitext(filename)
#     fn = sha256((str(datetime.now()) + name).encode('utf-8')).hexdigest() + ext
#     return "order-images/" + fn
#
#
# # TOFIX сделать удаление ненужных картинок из файловой системы
#
# class OrderImage(models.Model):
#     MAX_IMAGES = 10
#
#     order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="order", verbose_name="Заказ")
#     image = models.ImageField(upload_to=_upload_image_filename, verbose_name="Картинка")
#
#     def __str__(self):
#         return f"{self.id}: {self.order}"
#
#     def save(self, *args, **kwargs):
#         super(OrderImage, self).save(*args, **kwargs)
#         img = Image.open(self.image.path)
#
#         if img.width > 1920 or img.height > 1080:
#             # нужно урезать фортку так, чтобы пропорции уменьшились пропорционально
#             output_size = (1920, 1080)
#             img.thumbnail(output_size)
#             img.save(self.image.path)
#
#
# class OrderRespond(models.Model):
#     create_time = models.DateTimeField(default=datetime.now, editable=False, verbose_name="Время отклика")
#     user = models.ForeignKey(SiteUser, on_delete=models.CASCADE, related_name="respond_user", verbose_name="Пользователь")
#     order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="respond_order", verbose_name="Заказ")
#
#     class Meta:
#         constraints = [
#             models.UniqueConstraint(
#                 fields=['user', 'order'], name='unique_order_respond_user_order'
#             )
#         ]
#
#     def __str__(self):
#         return f"{self.order}: {self.user}"
#
#     def save(self, *args, **kwargs):
#         if Order.objects.get(id=self.order.id) == self.user.id:
#             raise Exception("User can't respond to self order")
#
#         self.full_clean()
#
#         super().save(*args, **kwargs)
