from datetime import datetime

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import JSONField
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.utils import timezone

from intranet.search.core.ferryman_client import FerrymanClient


User = get_user_model()


common_permissions = settings.ISEARCH.get('permissions', [])

SEARCH_PERMISSION_PREFIX = 'view_'
search_permissions = [
    f'{SEARCH_PERMISSION_PREFIX}{s}'
    for s in settings.ISEARCH['scopes']
]

SUGGEST_PERMISSION_PREFIX = 'view_suggest_'
SUGGEST_LAYERS = [s for s in settings.ISEARCH['suggest']['layers']]
SUGGEST_SCOPES = [*SUGGEST_LAYERS, 'all']
suggest_permissions = [
    f'{SUGGEST_PERMISSION_PREFIX}{s}'
    for s in settings.ISEARCH['suggest']['layers']
]
SCOPE_NAMES = [scope_name for scope_name in settings.ISEARCH['scopes']]


class IndexRef(models.Model):
    search = models.CharField(max_length=50)
    index = models.CharField(max_length=50, default='')
    backend = models.CharField(max_length=10, default='platform',
                               choices=[('platform', 'platform')])

    class Meta:
        abstract = True


class RevisionRef(models.Model):
    revision = models.ForeignKey('Revision', null=True, default=None)

    class Meta:
        abstract = True


class Artefact(IndexRef, RevisionRef):
    class Meta:
        abstract = True


class OrganizationRef(models.Model):
    organization = models.ForeignKey('Organization')

    class Meta:
        abstract = True


class Indexation(IndexRef, RevisionRef):
    STATUS_NEW = 'new'
    STATUS_STOP = 'stop'
    STATUS_DONE = 'done'
    STATUS_PAUSED = 'paused'
    STATUS_FAIL = 'fail'
    STATUSES = (STATUS_NEW, STATUS_STOP, STATUS_DONE, STATUS_PAUSED, STATUS_FAIL)

    start_time = models.DateTimeField(db_index=True)
    end_time = models.DateTimeField(null=True)
    last_check_at = models.DateTimeField(null=True)

    documents_count = models.PositiveIntegerField(null=True)
    expected_documents_count = models.PositiveIntegerField(null=True)
    status = models.CharField(max_length=10, null=True, default=STATUS_NEW,
                              choices=list(zip(STATUSES, STATUSES)))

    stdout = models.TextField(null=True)
    stderr = models.TextField(null=True)

    log = models.TextField(null=True)

    swarm = models.BooleanField(default=True)
    walk = models.BooleanField(default=True)
    setup = models.BooleanField(default=True)
    fetch = models.BooleanField(default=True)
    create = models.BooleanField(default=True)
    store = models.BooleanField(default=True)
    delete = models.BooleanField(default=True)
    load = models.BooleanField(default=True)
    content = models.BooleanField(default=True)

    options = JSONField(null=True, encoder=DjangoJSONEncoder)
    comment = models.TextField(null=True)
    user = models.CharField(max_length=250, null=True)
    stats = models.TextField(null=True)
    last_statist = models.DateTimeField(null=True)
    load_conf = JSONField(null=True)
    last_wiki_pk = models.IntegerField(
        null=True,
        help_text='Storing the value of `pk` param and changing it during indexation. '
                  'Starting new indexation after a pause with this value being default.',
    )

    class Meta:
        index_together = [('status', 'backend')]
        db_table = 'isearch_indexation'


class IndexationStats(RevisionRef):
    indexation = models.ForeignKey(Indexation, null=True)
    hostname = models.CharField(max_length=255)
    updated_at = models.DateTimeField(auto_now=True)
    stats = JSONField(null=True)
    tick = models.PositiveIntegerField(default=0)

    class Meta:
        unique_together = ('indexation', 'hostname')
        db_table = 'isearch_indexationstats'


class Feature(models.Model):
    user = models.CharField(max_length=255, db_index=True, null=True, default=None)
    name = models.CharField(max_length=255)
    group_id = models.PositiveIntegerField(null=True, default=None)
    value = models.CharField(max_length=255)

    class Meta:
        unique_together = ('user', 'name')
        db_table = 'isearch_feature'


class GroupAttr(Artefact):
    name = models.CharField(max_length=255)
    label_ru = models.CharField(max_length=255)
    label_en = models.CharField(max_length=255, default='')
    url = models.CharField(max_length=255, default='')
    value = models.CharField(max_length=255, default='')

    class Meta:
        unique_together = [('search', 'index', 'backend', 'revision', 'name', 'value')]
        db_table = 'isearch_groupattr'


class Facet(Artefact):
    facet = models.CharField(max_length=255)
    value = models.CharField(max_length=255)
    label_ru = models.CharField(max_length=255)
    label_en = models.CharField(max_length=255)

    class Meta:
        unique_together = [('search', 'index', 'backend', 'revision', 'facet', 'value')]
        db_table = 'isearch_facet'


class Revision(IndexRef, OrganizationRef):
    STATUSES = (
        ('new',      'новый'),
        ('broken',   'сломанный'),
        ('ready',    'готовый'),
        ('deleted',  'удаленный'),
        ('active',   'активный'),
    )

    date = models.DateTimeField(auto_now_add=True)

    status = models.CharField(max_length=10, choices=STATUSES, default='new', db_index=True)
    service = models.CharField(max_length=64, default='')
    latest_indexation_time = models.DateTimeField(null=True, blank=True)

    class Meta:
        db_table = 'isearch_revision'


class Formula(models.Model):
    search = models.CharField(max_length=50)
    index = models.CharField(max_length=50, default='')
    name = models.CharField(max_length=255, default='default')
    service = models.CharField(max_length=50, default='')
    polynomial = models.TextField()
    compiled = models.TextField()
    additional = models.CharField(max_length=255, default='')

    class Meta:
        unique_together = [('search', 'index', 'name', 'service')]
        db_table = 'isearch_formula'


class Factor(models.Model):
    factor_id = models.PositiveIntegerField(default=0)
    name = models.CharField(max_length=255)
    service = models.CharField(max_length=64, default='intrasearch')

    class Meta:
        unique_together = [('name', 'service')]
        db_table = 'isearch_factor'


class ExternalWizardRule(models.Model):
    search = models.CharField(max_length=50)
    index = models.CharField(max_length=50, default='')
    name = models.CharField(max_length=255, default='default')
    rule = models.TextField()

    # Дополнительные параметры, которые передаются в при запросах к SaaS.
    # В основном используются для передачи wizextra, но чтобы wizextra начал применяться,
    # необходимо, чтобе в конфиге searchproxy в блоке CgiParams
    # не было поля wizextra, иначе используются только значения из конфига.
    params = models.CharField(max_length=255, default='')

    class Meta:
        unique_together = [('search', 'index', 'name')]
        db_table = 'isearch_externalwizardrule'


class StageStatus(models.Model):
    STATUS_NEW = 'new'
    STATUS_DONE = 'done'
    STATUS_FAIL = 'fail'
    STATUS_CANCEL = 'cancel'
    STATUS_IN_PROGRESS = 'in progress'
    STATUS_RETRY = 'retry'
    STATUSES = (STATUS_NEW, STATUS_DONE, STATUS_FAIL, STATUS_CANCEL, STATUS_IN_PROGRESS, STATUS_RETRY)

    status_id = models.CharField(max_length=100, db_index=True)
    status = models.CharField(max_length=16, default=STATUS_NEW,
                              choices=list(zip(STATUSES, STATUSES)))

    class Meta:
        db_table = 'isearch_stagestatus'


class PushRecord(IndexRef):
    STATUS_NEW = 1
    STATUS_FAIL = 2
    STATUS_DONE = 3
    STATUS_CANCEL = 4
    STATUS_RETRY = 5
    STATUS_KNOWN_FAIL = 6
    STATUSES = (
        (STATUS_NEW, 'new'),
        (STATUS_FAIL, 'fail'),
        (STATUS_DONE, 'done'),
        (STATUS_CANCEL, 'cancel'),
        (STATUS_RETRY, 'retry'),
        (STATUS_KNOWN_FAIL, 'known_fail'),
    )

    date = models.DateTimeField(auto_now_add=True, db_index=True)
    type = models.CharField(max_length=100)
    meta = JSONField(null=True, encoder=DjangoJSONEncoder)
    comment = models.TextField(null=True, blank=True, default='')
    object_id = models.CharField(max_length=50, db_index=True, null=True, default=None)
    organization = models.ForeignKey('Organization', null=True, default=None, on_delete=models.CASCADE)

    status = models.SmallIntegerField(choices=STATUSES, default=STATUS_NEW, null=True)
    start_time = models.DateTimeField(null=True, db_index=True)
    end_time = models.DateTimeField(null=True)
    retries = models.IntegerField(default=0, null=True)

    class Meta:
        db_table = 'isearch_pushrecord'


class PushInstance(models.Model):
    STATUS_NEW = PushRecord.STATUS_NEW
    STATUS_FAIL = PushRecord.STATUS_FAIL
    STATUS_DONE = PushRecord.STATUS_DONE
    STATUS_CANCEL = PushRecord.STATUS_CANCEL
    STATUS_RETRY = PushRecord.STATUS_RETRY
    STATUSES = PushRecord.STATUSES

    push = models.ForeignKey(PushRecord, on_delete=models.CASCADE, related_name='instances')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(default=datetime.now)
    status = models.SmallIntegerField(choices=STATUSES, default=STATUS_NEW, null=True)

    revision = models.ForeignKey(Revision, null=True, default=None, on_delete=models.SET_NULL)
    indexation = models.ForeignKey(Indexation, null=True, default=None, on_delete=models.SET_NULL)

    class Meta:
        unique_together = [('push', 'indexation'), ('push', 'revision')]
        db_table = 'isearch_pushinstance'


class Job(models.Model):
    STATUSES = ('new', 'running', 'done', 'fail')

    start_time = models.DateTimeField(auto_now_add=True)
    end_time = models.DateTimeField(null=True)

    status = models.CharField(max_length=10, choices=[(i, i) for i in STATUSES],
                              default='new')

    entity = models.CharField(max_length=255)
    job_name = models.CharField(max_length=255, db_index=True)
    message = models.TextField(default='')
    options = models.TextField(default='', blank=True)

    class Meta:
        db_table = 'isearch_job'


class Node(models.Model):
    hostname = models.CharField(max_length=255)
    updated_at = models.DateTimeField(auto_now=True)
    info = JSONField(null=True)

    class Meta:
        db_table = 'isearch_node'


class Organization(models.Model):
    directory_id = models.PositiveIntegerField(unique=True)
    directory_revision = models.PositiveIntegerField(default=0)
    name = models.CharField(max_length=512)
    label = models.CharField(max_length=256)
    creation_date = models.DateTimeField(auto_now_add=True)
    deleted = models.BooleanField(default=False)
    deletion_date = models.DateTimeField(null=True)
    organization_type = models.CharField(max_length=256, default='common')

    class Meta:
        db_table = 'isearch_organization'
        permissions = list(zip(common_permissions, common_permissions))
        permissions.extend(zip(search_permissions, search_permissions))
        permissions.extend(zip(suggest_permissions, suggest_permissions))


class Service(OrganizationRef):
    """
    Модель для сохранения отношения организация - подключенные у организации сервисы
    (сервисы тут в понимании директории)

    Для внешнего инстанса варианты service_name пока: wiki, st, directory
    Для внутреннего инстанса service_name - это все search из настроек
    """
    name = models.CharField(max_length=256)

    class Meta:
        unique_together = [('name', 'organization')]
        db_table = 'isearch_service'

    def __str__(self):
        return '{}; Organization {}: {} {}'.format(
            self.name,
            self.organization_id,
            self.organization.directory_id,
            self.organization.label,
        )


class OrganizationRobotUser(models.Model):
    organization = models.OneToOneField(Organization, primary_key=True, related_name='robot_user')
    uid = models.BigIntegerField(db_index=True)
    token = models.CharField(max_length=127)
    expires_at = models.DateTimeField()

    class Meta:
        db_table = 'isearch_organizationrobotuser'


class Mark(models.Model):
    """
    Оценка релевантности документа на поисковой выдаче
    """
    SCORE_VITAL = 'vital'
    SCORE_RELEVANT = 'relevant'
    SCORE_IRRELEVANT = 'irrelevant'
    SCORES = (
        SCORE_IRRELEVANT,
        SCORE_RELEVANT,
        SCORE_VITAL,
    )
    SUGGEST_SCORES = (
        SCORE_IRRELEVANT,
        SCORE_VITAL,
    )
    SCORE_CHOICES = list(zip(SCORES, SCORES))
    SUGGEST_SCORE_CHOICES = list(zip(SUGGEST_SCORES, SUGGEST_SCORES))
    SEARCH_SCOPES = list(zip(SCOPE_NAMES, SCOPE_NAMES))
    SUGGEST_SCOPES = list(zip(SUGGEST_SCOPES, SUGGEST_SCOPES))
    ALL_SCOPES = [*SEARCH_SCOPES, *SUGGEST_SCOPES]
    MODES = [('suggest', 'suggest'), ('search', 'search'), ('', '')]
    SUGGEST_LAYERS = list(zip(SUGGEST_LAYERS, SUGGEST_LAYERS))

    request_id = models.CharField(max_length=64, default='')
    url = models.URLField(
        default=''
    )
    # url of the page with suggest
    href = models.URLField(
        default='',
    )
    score = models.CharField(
        max_length=32,
        choices=SCORE_CHOICES,
        default='',
    )
    text = models.TextField(default='')
    scope = models.CharField(
        max_length=32,
        choices=ALL_SCOPES,
        default='',
    )
    position = models.IntegerField(default=0)
    # layer of the result
    layer = models.CharField(
        max_length=32,
        default='',
    )
    # search mode
    mode = models.CharField(
        max_length=32,
        choices=MODES,
        default='',
    )
    uid = models.CharField(
        max_length=255,
        default='',
    )

    wizard_id = models.CharField(max_length=64, default='')

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(default=timezone.now)

    class Meta:
        db_table = 'isearch_mark'


class SuggestedAnswer(models.Model):
    """
    Предложенный витальный ответ
    """
    request_id = models.CharField(max_length=64)
    url = models.URLField(max_length=1024)
    scope = models.CharField(
        max_length=64,
        choices=list(zip(SCOPE_NAMES, SCOPE_NAMES)),
    )
    search_text = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(default=timezone.now)

    class Meta:
        db_table = 'isearch_answer'


class ClickedMarkedUrl(models.Model):
    """
    Кликнутые или отмеченные витальными или релевантными урлы
    """
    url = models.CharField(max_length=255)
    text = models.CharField(max_length=255)
    long_clicks_count = models.PositiveIntegerField(default=0)
    vital_marks_count = models.PositiveIntegerField(default=0)
    relevant_marks_count = models.PositiveIntegerField(default=0)

    def __str__(self):
        return self.url

    class Meta:
        unique_together = ('url', 'text')
        db_table = 'isearch_clickedmarkedurl'


class FerrymanTable(models.Model):
    """ Таблица в YT, отправленная в ferryman
    """
    STATUS_NEW = 'new'
    STATUS_QUEUE = FerrymanClient.BATCH_STATUS_QUEUE
    STATUS_PROCESSING = FerrymanClient.BATCH_STATUS_PROCESSING
    STATUS_FINAL = FerrymanClient.BATCH_STATUS_FINAL
    STATUS_ERROR = FerrymanClient.BATCH_STATUS_ERROR
    STATUSES = (
        STATUS_NEW,
        STATUS_QUEUE,
        STATUS_PROCESSING,
        STATUS_FINAL,
        STATUS_ERROR,
    )
    STATUS_CHOICES = list(zip(STATUSES, STATUSES))

    batch_id = models.CharField(max_length=64, unique=True)
    revision = models.ForeignKey(Revision)
    path = models.CharField(max_length=128)
    status = models.CharField(max_length=64, choices=STATUS_CHOICES, default=STATUS_NEW)

    class Meta:
        db_table = 'isearch_ferrymantable'


class MovedPage(models.Model):
    """
    Перенесённые страницы из одного источника в другой
    """
    old_url = models.CharField(max_length=1024)
    new_url = models.CharField(max_length=1024)

    class Meta:
        unique_together = [('old_url', 'new_url')]
