# Copyright (c) 2015-2016 Codethink Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from sqlalchemy.orm import aliased, subqueryload
from wsme.exc import ClientSideError

from storyboard._i18n import _
from storyboard.db.api import base as api_base
from storyboard.db.api import teams as teams_api
from storyboard.db.api import users as users_api
from storyboard.db import models


def _board_get(id, session=None):
    if not session:
        session = api_base.get_session()
    query = session.query(models.Board).options(
        subqueryload(models.Board.lanes)).filter_by(id=id)

    return query.first()


def get(id):
    return _board_get(id)


def _build_board_query(title=None, creator_id=None, user_id=None,
                       project_id=None, archived=False, current_user=None,
                       session=None, item_type=None, story_id=None,
                       task_id=None):
    query = api_base.model_query(models.Board, session=session).distinct()

    query = api_base.apply_query_filters(query=query,
                                         model=models.Board,
                                         title=title,
                                         creator_id=creator_id,
                                         project_id=project_id)

    # Filter out boards that the current user can't see
    query = api_base.filter_private_boards(query, current_user)

    # Filter by boards that a given user has permissions to use
    if user_id:
        board_permissions = aliased(models.board_permissions)
        permissions = aliased(models.Permission)
        user_permissions = aliased(models.user_permissions)
        users = aliased(models.User)
        query = query.join(
            (board_permissions,
             models.Board.id == board_permissions.c.board_id)
        )
        query = query.join(
            (permissions,
             board_permissions.c.permission_id == permissions.id)
        )
        query = query.join(
            (user_permissions,
             permissions.id == user_permissions.c.permission_id)
        )
        query = query.join((users, user_permissions.c.user_id == users.id))
        query = query.filter(users.id == user_id)

    # Filter by whether or not we want archived boards
    query = query.filter(models.Board.archived == archived)

    if story_id or task_id:
        query = query.join(models.BoardWorklist,
                           models.Worklist)

    # Filter by story id
    if story_id:
        query = query.join(models.WorklistItem)
        stories = query.filter(models.WorklistItem.item_type == 'story')
        tasks = query.filter(models.WorklistItem.item_type == 'task')
        if item_type == 'story':
            query = stories.filter(models.WorklistItem.item_id == story_id)
        elif item_type == 'task':
            tasks = tasks.join(
                (models.Task, models.WorklistItem.item_id == models.Task.id))
            query = tasks.filter(models.Task.story_id == story_id)
        else:
            stories = stories.filter(models.WorklistItem.item_id == story_id)
            tasks = tasks.join(
                (models.Task, models.WorklistItem.item_id == models.Task.id))
            tasks = tasks.filter(models.Task.story_id == story_id)
            query = stories.union(tasks)

    # Filter by task id
    if task_id:
        items = aliased(models.WorklistItem)
        query = query.join((items, models.Worklist.id == items.list_id))
        query = query.filter(items.item_type == 'task')
        query = query.filter(items.item_id == task_id)

    return query


def get_all(title=None, creator_id=None, user_id=None, project_id=None,
            task_id=None, story_id=None, archived=False, offset=None,
            limit=None, sort_field=None, sort_dir=None, current_user=None,
            item_type=None, **kwargs):
    if sort_field is None:
        sort_field = 'id'
    if sort_dir is None:
        sort_dir = 'asc'

    boards = _build_board_query(title=title,
                                creator_id=creator_id,
                                project_id=project_id,
                                user_id=user_id,
                                archived=archived,
                                current_user=current_user,
                                item_type=item_type,
                                story_id=story_id,
                                task_id=task_id,
                                **kwargs)

    boards = api_base.paginate_query(query=boards,
                                     model=models.Board,
                                     limit=limit,
                                     offset=offset,
                                     sort_key=sort_field,
                                     sort_dir=sort_dir)
    return boards.all()


def get_count(title=None, creator_id=None, user_id=None, project_id=None,
              task_id=None, story_id=None, item_type=None, archived=False,
              current_user=None, **kwargs):
    query = _build_board_query(title=title,
                               creator_id=creator_id,
                               project_id=project_id,
                               user_id=user_id,
                               archived=archived,
                               current_user=current_user,
                               story_id=story_id,
                               task_id=task_id,
                               item_type=item_type,
                               **kwargs)
    return query.count()


def create(values):
    board = api_base.entity_create(models.Board, values)
    return board


def update(id, values):
    return api_base.entity_update(models.Board, id, values)


def add_lane(board, lane_dict):
    # Make sure we're adding the lane to the right board
    lane_dict['board_id'] = board.id

    if lane_dict.get('list_id') is None:
        raise ClientSideError(_("A lane must have a worklist_id."))

    if lane_dict.get('position') is None:
        lane_dict['position'] = len(board.lanes)

    api_base.entity_create(models.BoardWorklist, lane_dict)

    return board


def update_lane(board, lane, new_lane):
    # Make sure we aren't messing up the board ID
    new_lane['board_id'] = board.id

    if new_lane.get('list_id') is None:
        raise ClientSideError(_("A lane must have a worklist_id."))

    api_base.entity_update(models.BoardWorklist, lane.id, new_lane)


def get_from_lane(worklist):
    lanes = api_base.entity_get_all(models.BoardWorklist,
                                    list_id=worklist.id)
    if lanes:
        lane = lanes[0]
        return lane.board


# FIXME: This assumes that boards only contain a task or story once, which
#        is not actually enforced when creating a card.
def get_card(board, item_type, item_id, archived=False):
    for lane in board.lanes:
        for card in lane.worklist.items:
            if (card.item_type == item_type
                    and card.item_id == item_id
                    and card.archived == archived):
                return card


def has_card(board, item_type, item_id):
    for lane in board.lanes:
        for item in lane.worklist.items:
            if item.item_id == item_id and item.item_type == item_type:
                return True
    return False


def get_user_owners(board):
    for permission in board.permissions:
        if permission.codename == 'edit_board':
            return [user.id for user in permission.users]


def get_user_users(board):
    for permission in board.permissions:
        if permission.codename == 'move_cards':
            return [user.id for user in permission.users]


def get_team_owners(board):
    for permission in board.permissions:
        if permission.codename == 'edit_board':
            return [team.id for team in permission.teams]


def get_team_users(board):
    for permission in board.permissions:
        if permission.codename == 'move_cards':
            return [team.id for team in permission.teams]


def get_permissions(board, user_id):
    user = users_api.user_get(user_id)
    if user is not None:
        valid_permissions = set(user.permissions)
        for team in user.teams:
            valid_permissions.update(team.permissions)
        return [permission.codename for permission in board.permissions
                if permission in valid_permissions]
    return []


def create_permission(board_id, permission_dict, session=None):
    board = _board_get(board_id, session=session)
    users = permission_dict.pop('users')
    teams = permission_dict.pop('teams')
    permission = api_base.entity_create(
        models.Permission, permission_dict, session=session)
    board.permissions.append(permission)
    for user_id in users:
        user = users_api.user_get(user_id, session=session)
        user.permissions.append(permission)
    for team_id in teams:
        team = teams_api.team_get(team_id, session=session)
        team.permissions.append(permission)
    return permission


def update_permission(board_id, permission_dict):
    board = _board_get(board_id)
    id = None
    for permission in board.permissions:
        if permission.codename == permission_dict['codename']:
            id = permission.id
    users = permission_dict.pop('users', None)
    teams = permission_dict.pop('teams', None)
    if users is not None:
        permission_dict['users'] = [users_api.user_get(user_id)
                                    for user_id in users]
    if teams is not None:
        permission_dict['teams'] = [teams_api.team_get(team_id)
                                    for team_id in teams]

    if id is None:
        raise ClientSideError(_("Permission %s does not exist")
                              % permission_dict['codename'])
    return api_base.entity_update(models.Permission, id, permission_dict)


def visible(board, user=None):
    if not board:
        return False
    if user and board.private:
        permissions = get_permissions(board, user)
        return any(name in permissions
                   for name in ['edit_board', 'move_cards'])
    return not board.private


def editable(board, user=None):
    if not board:
        return False
    if not user:
        return False
    return 'edit_board' in get_permissions(board, user)
