# Copyright (c) 2014 Mirantis Inc.
# Copyright (c) 2016 Codethink Ltd
# Copyright (c) 2019 Adam Coldrick
#
# 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 datetime import datetime

from pecan import request
from wsme import types as wtypes

from storyboard.api.v1 import base
from storyboard.common.custom_types import NameType
from storyboard.common import event_resolvers
from storyboard.db.api import base as api_base
from storyboard.db.api import boards as boards_api
from storyboard.db.api import comments as comments_api
from storyboard.db.api import due_dates as due_dates_api
from storyboard.db.api import stories as stories_api
from storyboard.db.api import tasks as tasks_api
from storyboard.db.api import worklists as worklists_api
from storyboard.db import models


def nodoc(obj):
    """Add a flag which autodoc can use to decide whether to skip a member."""
    obj.nodoc = True
    return obj


class Comment(base.APIBase):
    """Any user may leave comments on stories."""

    content = wtypes.text
    """The content of the comment."""

    is_active = bool
    """Is this an active comment, or has it been deleted?"""

    in_reply_to = int
    """The ID of the parent comment, if any."""

    @classmethod
    def sample(cls):
        return cls(
            id=5,
            created_at=datetime.fromtimestamp(1321009871),
            content="A sample comment body.",
            is_active=True,
            in_reply_to=2)


class SystemInfo(base.APIBase):
    """Represents the system information for Storyboard
    """

    config = {wtypes.text: bool}
    """Information about enabled features from configuration."""

    version = wtypes.text
    """The application version."""

    @classmethod
    def sample(cls):
        return cls(
            config={"enable_editable_comments": True},
            version="338c2d6")


class Project(base.APIBase):
    """The Storyboard Registry describes the open source world as ProjectGroups
    and Projects. Each ProjectGroup may be responsible for several Projects.
    For example, the OpenStack Infrastructure ProjectGroup has Zuul, Nodepool,
    Storyboard as Projects, among others.
    """

    name = NameType()
    """The Project unique name. This name will be displayed in the URL.
    At least 3 alphanumeric symbols. Minus and dot symbols are allowed as
    separators.
    """

    description = wtypes.text
    """Details about the project's work, highlights, goals, and how to
    contribute. Use plain text, paragraphs are preserved and URLs are
    linked in pages.
    """

    is_active = bool
    """Is this an active project, or has it been deleted?"""

    repo_url = wtypes.text
    """This is a repo link for this project"""

    autocreate_branches = bool
    """This flag means that storyboard will try to create task branches
    automatically from the branches declared in the code repository.
    """

    @classmethod
    def sample(cls):
        return cls(
            id=3,
            created_at=datetime.fromtimestamp(1321009871),
            name="StoryBoard",
            description="This is an awesome project.",
            is_active=True,
            repo_url="https://git.openstack.org/openstack-infra/storyboard.git"
        )


class ProjectGroup(base.APIBase):
    """Represents a group of projects."""

    name = NameType()
    """The Project Group unique name. This name will be displayed in the URL.
    At least 3 alphanumeric symbols. Minus and dot symbols are allowed as
    separators.
    """

    title = wtypes.text
    """The full name of the project group, which can contain spaces, special
    characters, etc.
    """

    @classmethod
    def sample(cls):
        return cls(
            id=1,
            created_at=datetime.fromtimestamp(1321009871),
            name="Infra",
            title="Awesome projects")


TaskStatuses = wtypes.Enum(wtypes.text, 'todo', 'inprogress',
    'invalid', 'review', 'merged')


class TaskStatusCount(base.APIBase):
    """Represents a task status and number of occurrences within a story."""
    key = wtypes.wsattr(TaskStatuses)
    count = int

    @classmethod
    def sample(cls):
        return cls(key='todo', count=2)


class User(base.APIBase):
    """Represents a user."""

    full_name = wtypes.text
    """Full (Display) name."""

    openid = wtypes.text
    """The unique identifier, returned by an OpenId provider"""

    email = wtypes.text
    """Email Address."""

    # Todo(nkonovalov): use teams to define superusers
    is_superuser = bool
    """Whether or not the user has superuser permissions."""

    last_login = datetime
    """Date of the last login."""

    enable_login = bool
    """Whether this user is permitted to log in."""

    @classmethod
    def sample(cls):
        return cls(
            id=3,
            full_name="Bart Simpson",
            openid="https://login.launchpad.net/+id/Abacaba",
            email="skinnerstinks@springfield.net",
            is_staff=False,
            is_active=True,
            is_superuser=True,
            last_login=datetime(2014, 1, 1, 16, 42))


class Team(base.APIBase):
    """The Team is a group of Users with a fixed set of permissions."""

    name = NameType()
    """The Team unique name. This name will be displayed in the URL.
    At least 3 alphanumeric symbols. Minus and dot symbols are allowed as
    separators."""

    description = wtypes.text
    """Details about the team."""

    security = bool
    """Whether or not the team is responsible for managing security issues."""

    @classmethod
    def sample(cls):
        return cls(
            name="StoryBoard-core",
            description="Core reviewers of StoryBoard team.",
            security=False)


class Story(base.APIBase):
    """The Story is the main element of StoryBoard. It represents a user story
    (generally a bugfix or a feature) that needs to be implemented. It will be
    broken down into a series of Tasks, which will each target a specific
    Project and branch.
    """

    title = wtypes.text
    """A descriptive label for the story, to show in listings."""

    description = wtypes.text
    """A complete description of the goal this story wants to cover."""

    is_bug = bool
    """Is this a bug or a feature :)"""

    creator_id = int
    """User ID of the Story creator"""

    story_type_id = int
    """ID of story type"""

    status = wtypes.text
    """The derived status of the story, one of 'active', 'merged', 'invalid'"""

    task_statuses = wtypes.ArrayType(TaskStatusCount)
    """The summary of each tasks/status."""

    tags = wtypes.ArrayType(wtypes.text)
    """Tag list assigned to this story."""

    due_dates = wtypes.ArrayType(int)
    """List of IDs of Due Dates which are related to this story."""

    private = bool
    """Whether or not this story is private."""

    security = bool
    """Whether or not this story is security-related."""

    users = wtypes.ArrayType(User)
    """The set of users with permission to see this story if it is private."""

    teams = wtypes.ArrayType(Team)
    """The set of teams with permission to see this story if it is private."""

    @classmethod
    def sample(cls):
        return cls(
            id=425,
            created_at=datetime.fromtimestamp(1321009871),
            title="Use Storyboard to manage Storyboard",
            description="We should use Storyboard to manage Storyboard.",
            is_bug=False,
            creator_id=1,
            task_statuses=[TaskStatusCount(key='todo', count=2)],
            story_type_id=1,
            status="active",
            tags=["t1", "t2"])

    @nodoc
    def summarize_task_statuses(self, story_summary):
        """Populate the task_statuses array."""
        self.task_statuses = []
        for task_status in models.Task.TASK_STATUSES:
            task_count = TaskStatusCount(
                key=task_status, count=getattr(story_summary, task_status))
            self.task_statuses.append(task_count)

    @nodoc
    def resolve_users(self, story):
        """Resolve the people who can see the story."""
        users = [api_base._filter_non_public_fields(user, user._public_fields)
                 for user in story.permissions[0].users]
        self.users = [User.from_db_model(user) for user in users]

    @nodoc
    def resolve_teams(self, story):
        """Resolve the teams who can see the story."""
        self.teams = [Team.from_db_model(team)
                      for team in story.permissions[0].teams]


class Tag(base.APIBase):

    name = wtypes.text
    """The tag name"""

    popularity = int
    """The number of stories with this tag"""

    @classmethod
    def sample(cls):
        return cls(name="low_hanging_fruit")

    @nodoc
    def set_popularity(self, tag):
        self.popularity = len(tag.stories)


class Task(base.APIBase):
    """A Task represents an actionable work item, targeting a specific Project
    and a specific branch. It is part of a Story. There may be multiple tasks
    in a story, pointing to different projects or different branches. Each task
    is generally linked to a code change proposed in Gerrit.
    """

    title = wtypes.text
    """An optional short label for the task, to show in listings."""

    status = wtypes.wsattr(TaskStatuses)
    """The type of statuses allowed ['todo', 'inprogress',
    'invalid', 'review', 'merged'].
    """

    creator_id = int
    """Id of the User who has created this Task"""

    story_id = int
    """The ID of the corresponding Story."""

    link = wtypes.text
    """A related resource for this task."""

    project_id = int
    """The ID of the corresponding Project."""

    assignee_id = int
    """The ID of the invidiual to whom this task is assigned."""

    branch_id = int
    """The ID of corresponding Branch"""

    milestone_id = int
    """The ID of corresponding Milestone"""

    due_dates = wtypes.ArrayType(int)
    """The IDs of due dates related to this task."""

    @classmethod
    def sample(cls):
        return cls(
            title="My really important task.",
            status="todo",
            creator_id=1,
            story_id=3456,
            link="Some notes about the task, this name is bad.",
            project_id=1,
            assignee_id=1,
            branch_id=1,
            milestone_id=1,
            due_dates=[1, 2, 3])


class Attachment(base.APIBase):
    """Represents metadata about a file attachment."""

    name = wtypes.text
    """The name of this attachment."""

    link = wtypes.text
    """The link to the actual attachment for download."""

    creator_id = int
    """The ID of the user who created this attachment."""

    story_id = int
    """The story that this attachment is related to."""

    @classmethod
    def sample(cls):
        return cls(
            name="logs.tar",
            link="https://example.org/v1/AUTH_test/storyboard/logs.tar",
            creator_id=1,
            story_id=1
        )


class Branch(base.APIBase):
    """Represents a branch."""

    name = wtypes.text
    """The branch unique name. This name will be displayed in the URL.
    At least 3 alphanumeric symbols.
    """

    project_id = int
    """The ID of the corresponding Project."""

    expired = bool
    """A binary flag that marks branches that should no longer be
    selectable in tasks."""

    expiration_date = datetime
    """Last date the expired flag was switched to True."""

    autocreated = bool
    """A flag that marks autocreated entries, so that they can
    be auto-expired when the corresponding branch is deleted in the git repo.
    """

    restricted = bool
    """This flag marks branch as restricted."""

    @classmethod
    def sample(cls):
        return cls(
            name="Storyboard-branch",
            project_id=1,
            expired=True,
            expiration_date=datetime(2015, 1, 1, 1, 1),
            autocreated=False,
            restricted=False
        )


class Milestone(base.APIBase):
    """Represents a milestone."""

    name = wtypes.text
    """The milestone unique name. This name will be displayed in the URL.
    At least 3 alphanumeric symbols.
    """

    branch_id = int
    """The ID of the corresponding Branch."""

    expired = bool
    """a binary flag that marks milestones that should no longer be
    selectable in completed tasks."""

    expiration_date = datetime
    """Last date the expired flag was switched to True."""

    @classmethod
    def sample(cls):
        return cls(
            name="Storyboard-milestone",
            branch_id=1,
            expired=True,
            expiration_date=datetime(2015, 1, 1, 1, 1)
        )


class TimeLineEvent(base.APIBase):
    """An event object should be created each time a story or a task state
    changes.
    """

    event_type = wtypes.text
    """This type should serve as a hint for the web-client when rendering
    a comment."""

    event_info = wtypes.text
    """A JSON encoded field with details about the event."""

    story_id = int
    """The ID of the corresponding Story."""

    worklist_id = int
    """The ID of the corresponding Worklist."""

    board_id = int
    """The ID of the corresponding Board."""

    author_id = int
    """The ID of User who has left the comment."""

    comment_id = int
    """The id of a comment linked to this event."""

    comment = Comment
    """The resolved comment."""

    @classmethod
    def sample(cls):
        return cls(
            id=45543,
            event_type="story_created",
            event_info='{"story_id": 100, "story_title": "A story"}',
            story_id=100)

    @staticmethod
    def resolve_event_values(event):
        if event.comment_id:
            comment = comments_api.comment_get(event.comment_id)
            event.comment = Comment.from_db_model(comment)

        event = TimeLineEvent._resolve_info(event)

        return event

    @staticmethod
    def _resolve_info(event):
        return event_resolvers.resolvers[event.event_type](event)


class RefreshToken(base.APIBase):
    """Represents a user refresh token."""

    user_id = int
    """The ID of corresponding user."""

    refresh_token = wtypes.text
    """The refresh token."""

    expires_in = int
    """The number of seconds after creation when this token expires."""

    @classmethod
    def sample(cls):
        return cls(
            user_id=1,
            refresh_token="a_unique_refresh_token",
            expires_in=3600
        )


class AccessToken(base.APIBase):
    """Represents a user access token."""

    user_id = int
    """The ID of User to whom this token belongs."""

    access_token = wtypes.text
    """The access token."""

    expires_in = int
    """The number of seconds after creation when this token expires."""

    refresh_token = RefreshToken
    """The associated refresh token."""

    @classmethod
    def sample(cls):
        return cls(
            user_id=1,
            access_token="a_unique_access_token",
            expires_in=3600)


class TaskStatus(base.APIBase):
    key = wtypes.text
    name = wtypes.text


class FilterCriterion(base.APIBase):
    """Represents a filter used to construct an automatic worklist."""

    title = wtypes.wsattr(wtypes.text, mandatory=True)
    """The title of the criterion, as displayed in the UI."""

    filter_id = int
    """The ID of the WorklistFilter this criterion is for."""

    negative = bool
    """Whether to return all items matching or not matching the criterion."""

    value = wtypes.text
    """The value to use as a criterion."""

    field = wtypes.text
    """The field to filter by."""

    @classmethod
    def sample(cls):
        return cls(
            title='TaskStatus',
            filter_id=1,
            negative=True,
            value='merged',
            field='status')


WorklistFilterType = wtypes.Enum(wtypes.text, 'Story', 'Task')


class WorklistFilter(base.APIBase):
    """Represents a set of criteria to filter items using AND."""

    type = wtypes.wsattr(WorklistFilterType, mandatory=True)
    """The type of objects to filter, Story or Task."""

    list_id = int
    """The ID of the Worklist this filter is for."""

    filter_criteria = wtypes.wsattr(wtypes.ArrayType(FilterCriterion),
                                    mandatory=True)
    """The list of criteria to apply."""

    @nodoc
    def resolve_criteria(self, filter):
        self.filter_criteria = [FilterCriterion.from_db_model(criterion)
                                for criterion in filter.criteria]

    @classmethod
    def sample(cls):
        return cls(
            type='Task',
            list_id=1,
            filter_criteria=[FilterCriterion(
                type='Task',
                title='TaskStatus',
                filter_id=1,
                negative=True,
                value='merged',
                field='status')
            ])


class DueDate(base.APIBase):
    """Represents a due date for tasks/stories."""

    name = wtypes.text
    """The name of the due date."""

    date = datetime
    """The date of the due date"""

    private = bool
    """A flag to identify whether this is a private or public due date."""

    creator_id = int
    """The ID of the user that created the DueDate."""

    tasks = wtypes.ArrayType(Task)
    """A list containing all the tasks with this due date."""

    stories = wtypes.ArrayType(Story)
    """A list containing all the stories with this due date."""

    count = int
    """The number of tasks and stories with this dues date."""

    owners = wtypes.ArrayType(int)
    """A list of the IDs of the users who can change this date."""

    users = wtypes.ArrayType(int)
    """A list of the IDs of the users who can assign this date to tasks."""

    board_id = int
    """The ID of a board which contains this due date.

    Used by PUT requests adding the due date to a board."""

    worklist_id = int
    """The ID of a worklist which contains this due date.

    Used by PUT requests adding the due date to a worklist."""

    editable = bool
    """Whether or not the due date is editable by the request sender."""

    assignable = bool
    """Whether or not the due date is assignable by the request sender."""

    @classmethod
    def sample(cls):
        return cls(
            id=1,
            name='A really great deadline',
            date=datetime(2016, 5, 30, 10, 10),
            private=False,
            creator_id=3,
            owners=[3],
            users=[],
            tasks=[],
            stories=[],
            count=0,
            editable=True,
            assignable=True)

    @nodoc
    def resolve_count_in_board(self, due_date, board):
        self.count = 0
        for lane in board.lanes:
            for card in worklists_api.get_visible_items(
                lane.worklist, current_user=request.current_user_id):
                if card.display_due_date == due_date.id:
                    self.count += 1

    @nodoc
    def resolve_items(self, due_date):
        """Resolve the various lists for the due date."""
        stories, tasks = due_dates_api.get_visible_items(
            due_date, current_user=request.current_user_id)
        self.tasks = [Task.from_db_model(task) for task in tasks]
        self.stories = [Story.from_db_model(story)
                        for story in stories]
        self.count = len(self.tasks) + len(self.stories)

    @nodoc
    def resolve_permissions(self, due_date, user=None):
        """Resolve the permissions groups of the due date."""
        self.owners = due_dates_api.get_owners(due_date)
        self.users = due_dates_api.get_users(due_date)
        self.editable = due_dates_api.editable(due_date, user)
        self.assignable = due_dates_api.assignable(due_date, user)


class WorklistItem(base.APIBase):
    """Represents an item in a worklist.

    The item could be either a story or a task.

    """
    list_id = int
    """The ID of the Worklist this item belongs to."""

    item_id = int
    """The ID of the Task or Story for this item."""

    item_type = wtypes.text
    """The type of this item, either "story" or "task"."""

    list_position = int
    """The position of this item in the Worklist."""

    archived = bool
    """Whether or not this item is archived."""

    display_due_date = int
    """The ID of the due date displayed on this item."""

    resolved_due_date = DueDate
    """The Due Date displayed on this item."""

    task = Task
    """The Task referred to by this list item."""

    story = Story
    """The Story referred to by this list item."""

    @classmethod
    def sample(cls):
        return cls(
            id=1,
            list_id=1,
            item_id=1,
            item_type="story",
            list_position=0,
            archived=False,
            display_due_date=None)

    @nodoc
    def resolve_due_date(self, worklist_item):
        if not worklist_item.display_due_date:
            self.resolved_due_date = None
            return
        due_date = due_dates_api.get(worklist_item.display_due_date)
        resolved = None
        if due_dates_api.visible(due_date, request.current_user_id):
            resolved = DueDate.from_db_model(due_date)
        self.resolved_due_date = resolved

    @nodoc
    def resolve_item(self, item, story_cache, task_cache, due_dates=True):
        user_id = request.current_user_id
        if item.item_type == 'story':
            story = story_cache.get(item.item_id) or stories_api.story_get(
                item.item_id, current_user=request.current_user_id)
            if story is None:
                return False
            self.story = Story.from_db_model(story)
            if due_dates:
                self.story.due_dates = [
                    date.id for date in story.due_dates
                    if due_dates_api.visible(date, user_id)
                ]
        elif item.item_type == 'task':
            task = task_cache.get(item.item_id) or tasks_api.task_get(
                item.item_id, current_user=request.current_user_id)
            if task is None or task.story is None:
                return False
            self.task = Task.from_db_model(task)
            if due_dates:
                self.task.due_dates = [
                    date.id for date in task.due_dates
                    if due_dates_api.visible(date, user_id)
                ]
        return True


class Worklist(base.APIBase):
    """Represents a worklist."""

    title = wtypes.text
    """The title of the worklist."""

    creator_id = int
    """The ID of the User who created this worklist."""

    project_id = int
    """The ID of the Project this worklist is associated with."""

    permission_id = int
    """The ID of the Permission which defines who can edit this worklist."""

    private = bool
    """A flag to identify if this is a private or public worklist."""

    archived = bool
    """A flag to identify whether or not the worklist has been archived."""

    automatic = bool
    """A flag to identify whether the contents are obtained by a filter or are
    stored in the database."""

    filters = wtypes.ArrayType(WorklistFilter)
    """A list of filters used if this is an "automatic" worklist."""

    owners = wtypes.ArrayType(int)
    """A list of the IDs of the users who have full permissions."""

    users = wtypes.ArrayType(int)
    """A list of the IDs of the users who can move items in the worklist."""

    team_owners = wtypes.ArrayType(int)
    """A list of the IDs of the teams who have full permissions."""

    team_users = wtypes.ArrayType(int)
    """A list of the IDs of the teams who can move items in the worklist."""

    items = wtypes.ArrayType(WorklistItem)
    """The items in the worklist."""

    @classmethod
    def sample(cls):
        return cls(
            id=1,
            title="My great worklist",
            creator_id=1,
            private=False,
            archived=False,
            automatic=False,
            filters=[],
            owners=[1],
            users=[2],
            items=[])

    @nodoc
    def resolve_items(self, worklist, story_cache={}, task_cache={}):
        """Resolve the contents of this worklist."""
        self.items = []
        user_id = request.current_user_id
        if worklist.automatic:
            self._resolve_automatic_items(worklist, user_id)
        else:
            self._resolve_set_items(worklist, user_id, story_cache, task_cache)

    @nodoc
    def _resolve_automatic_items(self, worklist, user_id):
        items, stories, tasks = worklists_api.filter_items(worklist, user_id)
        story_cache = {story.id: story for story in stories}
        task_cache = {task.id: task for task in tasks}
        for item in items:
            item_model = WorklistItem(**item)
            valid = item_model.resolve_item(item_model, story_cache,
                                            task_cache, due_dates=False)
            if not valid:
                continue
            item_model.resolved_due_date = None
            self.items.append(item_model)

    @nodoc
    def _resolve_set_items(self, worklist, user_id, story_cache, task_cache):
        for item in worklist.items:
            if item.archived:
                continue
            item_model = WorklistItem.from_db_model(item)
            valid = item_model.resolve_item(item, story_cache, task_cache)
            if not valid:
                continue
            item_model.resolve_due_date(item)
            self.items.append(item_model)
        self.items.sort(key=lambda x: x.list_position)

    @nodoc
    def resolve_permissions(self, worklist):
        self.owners = worklists_api.get_user_owners(worklist)
        self.users = worklists_api.get_user_users(worklist)
        self.team_owners = worklists_api.get_team_owners(worklist)
        self.team_users = worklists_api.get_team_users(worklist)

    @nodoc
    def resolve_filters(self, worklist):
        self.filters = []
        for filter in worklist.filters:
            model = WorklistFilter.from_db_model(filter)
            model.resolve_criteria(filter)
            self.filters.append(model)


class Lane(base.APIBase):
    """Represents a lane in a kanban board."""

    board_id = int
    """The ID of the board containing the lane."""

    list_id = int
    """The ID of the worklist which represents the lane."""

    worklist = Worklist
    """The worklist which represents the lane."""

    position = int
    """The position of the lane in the board."""

    @classmethod
    def sample(cls):
        return cls(
            id=1,
            board_id=23,
            list_id=10,
            position=1,
            worklist=Worklist(
                id=1,
                title="My great worklist",
                creator_id=1,
                private=False,
                archived=False,
                automatic=False,
                filters=[],
                owners=[1],
                users=[2],
                items=[]))

    @nodoc
    def resolve_list(self, lane, story_cache, task_cache, resolve_items=True):
        """Resolve the worklist which represents the lane."""
        self.worklist = Worklist.from_db_model(lane.worklist)
        self.worklist.resolve_permissions(lane.worklist)
        self.worklist.resolve_filters(lane.worklist)
        if resolve_items:
            self.worklist.resolve_items(
                lane.worklist, story_cache, task_cache)
        else:
            items = worklists_api.get_visible_items(
                lane.worklist, current_user=request.current_user_id)
            self.worklist.items = [WorklistItem.from_db_model(item)
                                   for item in items]


class Board(base.APIBase):
    """Represents a kanban board made up of worklists."""

    title = wtypes.text
    """The title of the board."""

    description = wtypes.text
    """The description of the board."""

    creator_id = int
    """The ID of the User who created this board."""

    project_id = int
    """The ID of the Project this board is associated with."""

    permission_id = int
    """The ID of the Permission which defines who can edit this board."""

    private = bool
    """A flag to identify whether this is a private or public board."""

    archived = bool
    """A flag to identify whether or not the board has been archived."""

    lanes = wtypes.ArrayType(Lane)
    """A list containing the representions of the lanes in this board."""

    due_dates = wtypes.ArrayType(DueDate)
    """A list containing the due dates used in this board."""

    owners = wtypes.ArrayType(int)
    """A list of the IDs of the users who have full permissions."""

    users = wtypes.ArrayType(int)
    """A list of the IDs of the users who can move cards in the board."""

    team_owners = wtypes.ArrayType(int)
    """A list of the IDs of the teams who have full permissions."""

    team_users = wtypes.ArrayType(int)
    """A list of the IDs of the teams who can move cards in the board."""

    @classmethod
    def sample(cls):
        return cls(
            id=1,
            title="StoryBoard development",
            description="Board for tracking StoryBoard development.",
            creator_id=1,
            private=False,
            archived=False,
            lanes=[Lane(
                id=1,
                board_id=23,
                list_id=10,
                position=1,
                worklist=Worklist(
                    id=1,
                    title="My great worklist",
                    creator_id=1,
                    private=False,
                    archived=False,
                    automatic=False,
                    filters=[],
                    owners=[1],
                    users=[2],
                    items=[]))],
            due_dates=[],
            owners=[1],
            users=[],
            team_owners=[1, 2],
            team_users=[])

    @nodoc
    def resolve_lanes(self, board, story_cache={}, task_cache={},
                      resolve_items=True):
        """Resolve the lanes of the board."""
        self.lanes = []
        for lane in board.lanes:
            lane_model = Lane.from_db_model(lane)
            lane_model.resolve_list(
                lane, story_cache, task_cache, resolve_items)
            self.lanes.append(lane_model)
        self.lanes.sort(key=lambda x: x.position)

    @nodoc
    def resolve_due_dates(self, board):
        self.due_dates = []
        for due_date in board.due_dates:
            if due_dates_api.visible(due_date, request.current_user_id):
                due_date_model = DueDate.from_db_model(due_date)
                due_date_model.resolve_items(due_date)
                due_date_model.resolve_permissions(
                    due_date, request.current_user_id)
                due_date_model.resolve_count_in_board(due_date, board)
                self.due_dates.append(due_date_model)

    @nodoc
    def resolve_permissions(self, board):
        """Resolve the permissions groups of the board."""
        self.owners = boards_api.get_user_owners(board)
        self.users = boards_api.get_user_users(board)
        self.team_owners = boards_api.get_team_owners(board)
        self.team_users = boards_api.get_team_users(board)
