from collections import UserDict

from assistant_bot.models.note import Note
from assistant_bot.utils.errors import NotFoundError


class NoteBook(UserDict):
    def __init__(self):
        super().__init__()
        self._next_id: int = 1

    def add_note(self, content: str = "") -> Note:
        note = Note(self._next_id, content)
        self.data[note.id] = note
        self._next_id += 1
        return note

    def find_by_id(self, note_id: int) -> Note | None:
        return self.data.get(note_id)

    def find_by_content(self, search_str: str) -> list[Note]:
        search_str = search_str.lower()
        return [
            note
            for note in self.data.values()
            if search_str in note.content.value.lower()
        ]

    def edit_note(self, note_id: int, new_content: str) -> Note:
        note = self.find_by_id(note_id)
        if note is None:
            raise NotFoundError(f"Note with id {note_id} not found")
        note.edit_content(new_content)
        return note

    def delete_note(self, note_id: int):
        if note_id in self.data:
            del self.data[note_id]
        else:
            raise NotFoundError(f"Note with id {note_id} not found")

    def get_all_tags(self) -> list[str]:
        tags = set()
        for note in self.data.values():
            tags.update(note.tags)
        return sorted(tags)

    def search_by_tag(self, tag: str) -> list[Note]:
        return [note for note in self.data.values() if tag.lower() in note.tags]

    def group_by_tags(self) -> dict[str, list[Note]]:
        tag_groups: dict[str, list[Note]] = {}
        for note in self.data.values():
            if not note.tags:
                tag_groups.setdefault("(no tags)", []).append(note)

            for tag in note.tags:
                tag_groups.setdefault(tag, []).append(note)
        return tag_groups

    def __str__(self) -> str:
        if not self.data:
            return "No notes found."
        return "\n".join(str(note) for note in self.data.values())
