"""Favro Data Model.

Based on the descriptions on the [Favro API Specification
Page](https://favro.com/developer/), and observed behaviour.
"""

import dataclasses
import datetime
from typing import Any


def map_opt(mapper, value):
    if value is not None:
        return mapper(value)
    return None


@dataclasses.dataclass(frozen=True)
class CollectionId:
    raw_id: int


@dataclasses.dataclass(frozen=True)
class SeqId:
    raw_id: int


@dataclasses.dataclass(frozen=True)
class CardId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class CardCommonId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class UserId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class OrganizationId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class CustomFieldId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class WidgetCommonId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class CustomFieldItemId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class TaskId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class TaskListId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class CardAssignment:
    user: UserId
    completed: bool

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'CardAssignment':
        return CardAssignment(
            UserId(json['userId']),
            json['completed'],
        )


@dataclasses.dataclass(frozen=True)
class UserInfo:
    user_id: UserId
    name: str
    email: str
    organization_role: str

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'UserInfo':
        return UserInfo(
            map_opt(UserId, json.get('userId')),
            json.get('name'),
            json.get('email'),
            json.get('organizationRole'),
        )


@dataclasses.dataclass(frozen=True)
class TagId:
    raw_id: str


@dataclasses.dataclass(frozen=True)
class TagInfo:
    tag_id: TagId
    organization_id: OrganizationId
    name: str
    color: str | None

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'TagInfo':
        return TagInfo(
            TagId(json['tagId']),
            OrganizationId(json['organizationId']),
            json['name'],
            json.get('color'),
        )


@dataclasses.dataclass(frozen=True)
class CustomFieldItem:
    """Custom field item object.
    Fields:
    - customFieldItemId: The id of the custom field item.
    - name: The name of the custom field item.
    """

    custom_field_item_id: CustomFieldItemId
    name: str

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'CustomFieldItem':
        return CustomFieldItem(
            custom_field_item_id=CustomFieldItemId(json['customFieldItemId']),
            name=json['name'],
        )


@dataclasses.dataclass(frozen=True)
class CustomFieldInfo:
    """Custom field object.

    Fields:
    - organizationId: The id of the organization that this custom field exists in.
    - customFieldId: The id of the custom field.
    - widgetCommonId: The shared id of the widget if the custom field is local to that widget.
    - type: field type. Refer to allowed custom field types.
    - name: name of the custom field.
    - enabled: True if the custom field is currently enabled for the organization.
    - customFieldItems: The list of items that this custom field can have in case it is a selectable one.
    """

    organization_id: OrganizationId
    custom_field_id: CustomFieldId
    widget_common_id: WidgetCommonId
    type: str
    name: str
    enabled: bool
    custom_field_items: list[CustomFieldItem]

    def get_field_item(
        self, field_item_id: CustomFieldItemId,
    ) -> CustomFieldItem | None:
        for item in self.custom_field_items:
            if item.custom_field_item_id == field_item_id:
                return item
        return None

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'CustomFieldInfo':
        return CustomFieldInfo(
            organization_id=OrganizationId(json['organizationId']),
            custom_field_id=CustomFieldId(json['customFieldId']),
            widget_common_id=map_opt(WidgetCommonId, json.get('widgetCommonId')),
            type=json['type'],
            name=json['name'],
            enabled=json['enabled'],
            custom_field_items=[
                CustomFieldItem.from_json(f) for f in json.get('customFieldItems', [])
            ],
        )


@dataclasses.dataclass(frozen=True)
class CustomField:
    custom_field_id: CustomFieldId
    value: list[CustomFieldItemId] | CustomFieldItemId
    color: str

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'CustomField':
        value = json.get('value')
        if value is None:
            typed_value = []
        elif isinstance(value, str):
            typed_value = CustomFieldItemId(value)
        else:
            typed_value = [CustomFieldItemId(v) for v in value]
        return CustomField(
            custom_field_id=CustomFieldId(json['customFieldId']),
            value=typed_value,
            color=json.get('color'),
        )


@dataclasses.dataclass(frozen=True)
class CardDependency:
    card_id: CardId
    card_common_id: CardCommonId
    is_before: bool
    reverse_card_id: CardId

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'CardDependency':
        return CardDependency(
            CardId(json['cardId']),
            CardCommonId(json['cardCommonId']),
            json['isBefore'],
            CardId(json['reverseCardId']),
        )


@dataclasses.dataclass(frozen=True)
class Task:
    task_id: TaskId
    task_list_id: TaskListId
    organization_id: OrganizationId
    card_common_id: CardCommonId
    name: str
    completed: bool
    position: int

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'Task':
        return Task(
            task_id=json['taskId'],
            task_list_id=json['taskListId'],
            organization_id=json['organizationId'],
            card_common_id=json['cardCommonId'],
            name=json['name'],
            completed=json['completed'],
            position=json['position'],
        )


@dataclasses.dataclass(frozen=True)
class Collection:
    collection_id: CollectionId
    organization_id: OrganizationId
    name: str
    shared_to_users: list[dict[str, str]]  # TODO
    public_sharing: str
    background: str
    is_archived: bool
    widget_common_id: WidgetCommonId

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'Collection':
        return Collection(
            collection_id=CollectionId(json['collectionId']),
            organization_id=OrganizationId(json['collectionId']),
            name=json['name'],
            shared_to_users=json['sharedToUsers'],
            public_sharing=json['publicSharing'],
            background=json['background'],
            is_archived=json['archived'],
            widget_common_id=map_opt(WidgetCommonId, json.get('widgetCommonId')),
        )


@dataclasses.dataclass(frozen=True)
class Card:
    card_id: CardId
    seq_id: SeqId
    common_id: CardCommonId
    organization_id: OrganizationId
    is_archived: bool
    name: str
    dependencies: list[CardDependency]
    assignments: list[CardAssignment]
    custom_fields: list[CustomField]
    tags: list[TagId]
    todo_list_user_id: UserId | None
    todo_list_completed: bool | None
    creator_user_id: UserId
    creation_date: datetime.datetime
    start_date: datetime.date | None
    due_date: datetime.date | None
    attachments: list[dict]
    detailed_description: str | None

    @staticmethod
    def from_json(json: dict[str, Any]) -> 'Card':
        card_id = CardId(json['cardId'])
        seq_id = SeqId(json['sequentialId'])
        return Card(
            card_id=card_id,
            seq_id=seq_id,
            common_id=CardCommonId(json['cardCommonId']),
            detailed_description=json.get('detailedDescription'),
            is_archived=json['archived'],
            organization_id=OrganizationId(json['organizationId']),
            name=json['name'],
            todo_list_user_id=map_opt(UserId, json.get('todoListUserId')),
            todo_list_completed=json.get('todoListCompleted'),
            dependencies=[
                CardDependency.from_json(dep) for dep in json['dependencies']
            ],
            tags=[TagId(tag) for tag in json['tags']],
            creator_user_id=UserId(json['createdByUserId']),
            creation_date=map_opt(
                datetime.datetime.fromisoformat,
                json.get('createdAt'),
            ),
            start_date=map_opt(datetime.datetime.fromisoformat, json.get('startDate')),
            due_date=map_opt(datetime.datetime.fromisoformat, json.get('dueDate')),
            assignments=[CardAssignment.from_json(ass) for ass in json['assignments']],
            custom_fields=[
                CustomField.from_json(field) for field in json['customFields']
            ],
            attachments=json['attachments'],  # TODO
        )