from collections import defaultdict
from datetime import date
from typing import Dict, List, Tuple, Optional

from django.conf import settings
from django.db import connection

from staff.lib.utils.list import divide_into_batches

from staff.map.errors import TableBookConflictType
from staff.map.models import FloorMap, Room, TableBook
from staff.map.models.utils import geom_to_polygon


def update_floor_img(floor_map):
    try:
        map_name = (
            FloorMap.objects
            .filter(floor_id=floor_map.floor_id, is_ready=True)
            .values_list('file_name', flat=True)
            .order_by('-created_at')
        )[0]
    except IndexError:
        map_name = ''
    floor_map.floor.img = map_name
    floor_map.floor.save()


def check_point_within(point, polygon):
    if not polygon:
        return False
    with connection.cursor() as c:
        c.execute("""
            SELECT ST_WithIn(
                ST_GeomFromText(%s),
                ST_GeomFromText(%s)
            )
        """, [point, polygon])
        row = c.fetchone()
        return row and row[0]


def get_map_room_id_to_room_square(room_square_scale: Optional[float] = None) -> Dict[int, float]:
    """
    Считает площадь комнат в м^2, умножая площади, посчитанные
    по координатам со Стаффа, на коэф-т `room_square_scale`
    """
    room_square_scale = room_square_scale or settings.ROOM_SQUARE_SCALE
    square_map = {}
    rooms_with_geometry = (
        Room.objects
        .active()
        .exclude(geometry='')
        .values('id', 'geometry')
    )
    rooms_with_geom_polygons = [
        (room['id'], geom_to_polygon(room['geometry']))
        for room in rooms_with_geometry
    ]
    for batch in divide_into_batches(rooms_with_geom_polygons, settings.ROOM_USAGE_BATCH_SIZE):
        query = 'SELECT ' + ','.join([
            f"ST_Area(ST_GeomFromText('{polygon}'))"
            for _, polygon in batch
        ])

        with connection.cursor() as c:
            c.execute(query)
            rows = c.fetchone()

            room_ids = [room_id for room_id, _ in batch]

            for i in range(len(rows)):
                square_map[room_ids[i]] = rows[i] * room_square_scale

    return square_map


def find_book_conflicts(books: List[TableBook]) -> List[Tuple[Tuple[TableBook, TableBook], TableBookConflictType]]:
    date_conflicts = []
    for conflict in find_book_date_conflicts(books):
        if conflict[0].staff_id == conflict[1].staff_id:
            date_conflicts.append((conflict, TableBookConflictType.BOOK_OVERLAP_ONE_PERSON))
        else:
            date_conflicts.append((conflict, TableBookConflictType.BOOK_OVERLAP))

    staff_conflicts = [
        (conflict, TableBookConflictType.STAFF_BOOKED_ANOTHER)
        for conflict in find_book_staff_conflicts(books)
    ]
    return date_conflicts + staff_conflicts


def find_book_date_conflicts(books: List[TableBook]) -> List[Tuple[TableBook, TableBook]]:
    """
    Ищет в books брони, пересекающиеся по датам
    :returns: список пар конфликтующих броней
    """
    books_sorted = sorted(books, key=lambda x: x.date_from)
    conflicts = []
    for i in range(len(books_sorted) - 1):
        if books_sorted[i].date_to >= books_sorted[i+1].date_from:
            conflicts.append((books_sorted[i], books_sorted[i+1]))

    return conflicts


def find_book_staff_conflicts(books: List[TableBook]) -> List[Tuple[TableBook, TableBook]]:
    """
    Ищет брони одного человека на одно время на разные столы
    :returns: список пар конфликтующих броней
    """
    if books:
        table_id = books[0].table_id
    else:
        return []

    conflicts = []

    staff_books = defaultdict(list)
    for book in books:
        staff_books[book.staff].append(book)

    staff_books_other_tables = (
        TableBook.objects
        .filter(
            staff__in=staff_books.keys(),
            date_to__gte=date.today(),
        )
        .exclude(table_id=table_id)
    )

    for book in staff_books_other_tables:
        staff_books[book.staff].append(book)

    for staff in staff_books:
        conflicts += find_book_date_conflicts(staff_books[staff])

    return [
        conflict for conflict in conflicts
        if conflict[0].table_id != conflict[1].table_id
    ]


def serialize_book(book: TableBook) -> dict:
    return {
        'book_id': book.id,
        'table_id': book.table_id,
        'date_from': book.date_from,
        'date_to': book.date_to,
        'staff_id': book.staff_id,
        'staff_login': book.staff.login,
    }


def serialize_book_conflict(
    conflict_books: Tuple[TableBook, TableBook],
    conflict_type: TableBookConflictType,
    table_id: int,
):
    older_book, newer_book = conflict_books
    result = {
        'overlap_start': newer_book.date_from,
        'overlap_finish': older_book.date_to,
    }
    if conflict_type == TableBookConflictType.BOOK_OVERLAP:
        result.update({
            'older_staff_id': older_book.staff.id,
            'newer_staff_id': newer_book.staff.id,
            'older_staff_login': older_book.staff.login,
            'newer_staff_login': newer_book.staff.login,
        })
    elif conflict_type == TableBookConflictType.BOOK_OVERLAP_ONE_PERSON:
        result.update({
            'staff_id': older_book.staff.id,
            'staff_login': older_book.staff.login,
        })
    elif conflict_type == TableBookConflictType.STAFF_BOOKED_ANOTHER:
        result.update({
            'staff_id': older_book.staff.id,
            'staff_login': older_book.staff.login,
            'other_table_id': (
                older_book.table_id
                if older_book.table_id != table_id
                else newer_book.table_id
            )
        })

    return result
