# standard
from dataclasses import dataclass, field

# plugin
from oslandia.helpdesk.api_graphql_manager import GraphQLRequestManager
from oslandia.helpdesk.markdown import append_watermark
from oslandia.helpdesk.user import User


@dataclass
class NotePermission:
    admin_note: bool = False
    create_note: bool = False


@dataclass
class Note:
    graphql_id: str
    body: str
    system: bool
    author: User
    created_at: str
    updated_at: str

    project_id: int
    user_permision: NotePermission | None = None


@dataclass
class Discussion:
    graphql_id: str
    project_id: int
    issue_graphql_id: str
    notes: list[Note] = field(default_factory=lambda: [])


class DiscussionGraphQLRequestManager(GraphQLRequestManager):
    """
    Fetches discussions for specific GitLab project and issue using GraphQL API.
    """

    def __init__(self, page_size: int = 5) -> None:
        """Constructor

        :param page_size: page size, defaults to 5
        :type page_size: int, optional
        """
        super().__init__()
        self.page_size = page_size

    def get_issue_discussions_for_cursor(
        self, project_path: str, issue_iid: int, after: str | None = None
    ) -> tuple[list[Discussion], str | None]:
        """Get discussion list for an issue and and a GraphQL cursor (None by default)

        :param project_path: full project path for GraphQL use
        :type project_path: str
        :param issue_iid: issue internal id
        :type issue_iid: int
        :param after: GraphQL cursor, None for first page
        :type after: str | None, optional
        :return: tuple with list of discussion and next GraphQL cursor
        :rtype: tuple[list[Discussion], str | None]
        """

        query = """
        query ($fullPath: ID!, $issue_iid: String, $after: String, $pageSize : Int) {
          project(fullPath: $fullPath) {
            id
            issue(iid: $issue_iid) {
              id
              discussions(after: $after,first:$pageSize) {
                nodes{
                  id
                  notes {
                    nodes{
                      id
                      body
                      system
                      createdAt
                      updatedAt
                      author {
                        id
                        username
                        name
                        avatarUrl
                        createdAt
                      }
                      userPermissions {
                            adminNote
                            createNote
                      }
                    }
                  }
                }
                pageInfo {
                    hasNextPage
                    endCursor
                }
              }
            }
          }
        }
        """

        # Define GraphQL variable
        variables = {
            "fullPath": project_path,
            "after": after,
            "issue_iid": str(issue_iid),
            "pageSize": self.page_size,
        }

        discussions: list[Discussion] = []
        after = None
        if response := self._post_query(query, variables):
            project_data = response["data"]["project"]
            if project_data is not None:
                issue_data = project_data["issue"]
                if issue_data is not None:
                    # Define project id for REST API from id
                    project_id = int(project_data["id"].split("/")[-1])
                    issue_graphql_id = issue_data["id"]
                    discussion_data = issue_data["discussions"]

                    for raw in discussion_data["nodes"]:
                        discussion = self._discussion_from_json(
                            raw, project_id, issue_graphql_id
                        )
                        discussions.append(discussion)

                    after = self._get_next_cursor(discussion_data)

        return discussions, after

    def create_issue_note(self, issue_id: int, body: str) -> Discussion | None:
        """Create a discussion with a note for an issue

        :param issue_id: issue id, will be used to define graphQL id : gid://gitlab/Issue/{issue_id}
        :type issue_id: int
        :param body: body for note creation
        :type body: str
        :return: created discussion, None in case of errors
        :rtype: Discussion | None
        """

        query = """
        mutation CreateNote($noteableId: NoteableID!, $body: String!) {
            createNote(input: {
                noteableId: $noteableId,
                body: $body
            }) {
                note {
                    id
                    body
                    system
                    createdAt
                    updatedAt
                    author {
                        id
                        username
                        name
                        avatarUrl
                        createdAt
                    }
                    userPermissions {
                        adminNote
                        createNote
                    }
                    project{
                      id
                    }
                    discussion{
                        id
                    }
                }
                errors
            }
        }
        """

        # Define GraphQL variable
        issue_graphql_id = f"gid://gitlab/Issue/{issue_id}"
        variables = {
            "noteableId": issue_graphql_id,
            "body": append_watermark(body),
        }

        discussion: Discussion | None = None
        if response := self._post_query(query, variables):
            create_data = response["data"]["createNote"]
            if create_data is not None:
                note_data = create_data["note"]
                project_data = note_data["project"]
                project_id = int(project_data["id"].split("/")[-1])
                note = self._note_from_json(raw=note_data, project_id=project_id)

                discussion_data = note_data["discussion"]

                discussion = Discussion(
                    graphql_id=discussion_data["id"],
                    project_id=project_id,
                    issue_graphql_id=issue_graphql_id,
                    notes=[note],
                )

        return discussion

    def update_issue_note(self, note_id: str, body: str) -> Note | None:
        """Update note with new body

        :param note_id: note id to update
        :type note_id: str
        :param body: new note body
        :type body: str
        :return: updated note, None in case of errors
        :rtype: Note | None
        """

        query = """
        mutation UpdateNote($id: NoteID!, $body: String!) {
            updateNote(input: {
                id: $id,
                body: $body
            }) {
                note {
                    id
                    body
                    system
                    createdAt
                    updatedAt
                    author {
                        id
                        username
                        name
                        avatarUrl
                        createdAt
                    }
                    userPermissions {
                        adminNote
                        createNote
                    }
                    project{
                      id
                    }
                }
                errors
            }
        }
        """

        # Define GraphQL variable
        variables = {"id": note_id, "body": body}

        note: Note | None = None
        if response := self._post_query(query, variables):
            create_data = response["data"]["updateNote"]
            if create_data is not None:
                note_data = create_data["note"]
                project_data = note_data["project"]
                project_id = int(project_data["id"].split("/")[-1])
                note = self._note_from_json(raw=note_data, project_id=project_id)

        return note

    def add_note_reply(
        self, issue_graphql_id: str, discussion_id: str, body: str
    ) -> Note | None:
        """Add reply to an issue discussion

        :param issue_graphql_id: issue graphql id
        :type issue_graphql_id: str
        :param discussion_id: discussion id
        :type discussion_id: str
        :param body: note body
        :type body: str
        :return: created note, None in case of errors
        :rtype: Note | None
        """
        query = """
        mutation CreateNote($noteableId: NoteableID!, $discussionId : DiscussionID!, $body: String!) {
            createNote(input: {
                noteableId: $noteableId,
                body: $body
                discussionId: $discussionId
            }) {
                note {
                    id
                    body
                    system
                    createdAt
                    updatedAt
                    author {
                        id
                        username
                        name
                        avatarUrl
                        createdAt
                    }
                    userPermissions {
                            adminNote
                            createNote
                    }
                    project{
                      id
                    }
                }
                errors
            }
        }
        """

        # Define GraphQL variable
        variables = {
            "noteableId": issue_graphql_id,
            "body": append_watermark(body),
            "discussionId": discussion_id,
        }

        note: Note | None = None
        if response := self._post_query(query, variables):
            create_data = response["data"]["createNote"]
            if create_data is not None:
                note_data = create_data["note"]
                project_data = note_data["project"]
                project_id = int(project_data["id"].split("/")[-1])
                note = self._note_from_json(
                    raw=create_data["note"], project_id=project_id
                )

        return note

    def _discussion_from_json(
        self, raw: dict, project_id: int, issue_graphql_id: str
    ) -> Discussion:
        """Create an discussion from json value

        :param raw: raw json value
        :type raw: dict
        :param project_id: discussion project id (not available in raw data)
        :type project_id: int
        :return: created discussion
        :rtype: Discussion
        """
        notes_data = raw["notes"]
        notes: list[Note] = []
        if notes_data is not None:
            for raw_note in notes_data["nodes"]:
                note = self._note_from_json(
                    raw_note,
                    project_id,
                )
                notes.append(note)

        return Discussion(
            graphql_id=raw["id"],
            project_id=project_id,
            notes=notes,
            issue_graphql_id=issue_graphql_id,
        )

    def _note_from_json(self, raw: dict, project_id: int) -> Note:
        """Create an note from json value

        :param raw: raw json value
        :type raw: dict
        :param project_id: note project id (not available in raw data)
        :type project_id: int
        :return: created note
        :rtype: Note
        """
        # Define issue id for REST API from id
        author_data = raw["author"]
        user_permission_data = raw["userPermissions"]

        return Note(
            graphql_id=raw["id"],
            body=raw["body"],
            system=raw["system"],
            author=User(
                id=int(author_data["id"].split("/")[-1]),
                username=author_data["username"],
                name=author_data["name"],
            ),
            created_at=raw["createdAt"],
            updated_at=raw["updatedAt"],
            project_id=project_id,
            user_permision=NotePermission(
                admin_note=user_permission_data["adminNote"],
                create_note=user_permission_data["createNote"],
            ),
        )
