import json
from dataclasses import dataclass
from typing import Optional

from django.apps import apps
from django.db import models
from django.urls import reverse
from scrobbles.dataclasses import BaseLogData
from scrobbles.mixins import LongPlayScrobblableMixin, ScrobblableConstants

BNULL = {"blank": True, "null": True}

TODOIST_TASK_URL = "https://app.todoist.com/app/task/{id}"


@dataclass
class TaskLogData(BaseLogData):
    title: Optional[str] = None
    labels: Optional[list[str]] = None

    orgmode_id: Optional[str] = None
    orgmode_state: Optional[str] = None
    orgmode_properties: Optional[dict] = None
    orgmode_drawers: Optional[list] = None
    orgmode_timestamps: Optional[list] = None

    todoist_id: Optional[str] = None
    todoist_project_id: Optional[str] = None

    _excluded_fields = {
        "labels",
        "orgmode_id",
        "orgmode_state",
        "orgmode_properties",
        "orgmode_drawers",
        "orgmode_timestamps",
        "todoist_id",
        "todoist_project_id",
    }

    @classmethod
    def override_fields(cls) -> dict:
        from scrobbles.forms import NotesDictField

        fields = {}
        for base in cls.mro()[1:]:
            if hasattr(base, "override_fields"):
                base_fields = base.override_fields()
                fields.update(base_fields)
        fields["notes"] = NotesDictField(required=False)
        return fields

    def notes_as_str(self, separator: str = " | ") -> str:
        """Return formatted notes with line breaks and no keys"""
        labels_str = ""
        if self.labels:
            labels_str = ", ".join(self.labels)

        lines = []
        if self.notes:
            notes = self.notes
            if isinstance(notes, dict):
                notes = [{k: v} for k, v in notes.items()]
            if isinstance(notes, str):
                notes = [notes]

            for note in notes:
                if isinstance(note, dict):
                    timestamp, note_text = next(iter(note.items()))
                    note_text = " ".join(note_text.strip().split())
                    lines.append(f"{timestamp}: {note_text}")
                if isinstance(note, list):
                    lines += note
                if isinstance(note, str):
                    lines.append(note)
        return separator.join(lines).encode("utf-8").decode("unicode_escape")

    def notes_as_html(self) -> str:
        import bleach
        import markdown
        from django.utils.safestring import mark_safe
        from scrobbles.dataclasses import BaseLogData

        if not self.notes:
            return ""

        md = markdown.Markdown(extensions=["extra"])
        allowed_tags = [
            "p", "br", "strong", "em", "a", "ul", "ol", "li",
            "code", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6",
            "hr", "img",
        ]

        notes = self.notes
        if isinstance(notes, dict):
            notes = [{k: v} for k, v in notes.items()]
        if isinstance(notes, str):
            notes = [notes]

        note_items = []
        for note in notes:
            if isinstance(note, dict):
                timestamp, note_text = next(iter(note.items()))
                note_items.append((timestamp, note_text.strip()))
            elif isinstance(note, str):
                escaped = note.encode("utf-8").decode("unicode_escape")
                for line in escaped.split("\n"):
                    if line.strip():
                        note_items.append((None, line.strip()))
            elif isinstance(note, list):
                for item in note:
                    if isinstance(item, str):
                        note_items.append((None, item.strip()))

        html_parts = []
        for i, (ts, text) in enumerate(note_items):
            if i > 0:
                html_parts.append('<hr class="note-divider">')

            ts_html = ""
            if ts:
                ts_html = f'<h5 class="note-timestamp">{BaseLogData._format_timestamp(ts)}</h5>'

            content_html = bleach.clean(
                md.convert(text),
                tags=allowed_tags,
                strip=True,
            )

            html_parts.append(
                f'<div class="note-item">{ts_html}<div class="note-content">{content_html}</div></div>'
            )

        return mark_safe("".join(html_parts))


class Task(LongPlayScrobblableMixin):
    """Basically a holder for Todoist Tasks
    and any other otherwise generic tasks.

    """

    description = models.TextField(**BNULL)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("tasks:task_detail", kwargs={"slug": self.uuid})

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants(verb="Doing", tags="memo")

    @property
    def logdata_cls(self):
        return TaskLogData

    def source_url_for_user(self, user_id) -> str:
        url = ""
        scrobble = self.scrobbles(user_id).first()
        if scrobble:
            if scrobble.log.get("source") == "todoist":
                url = TODOIST_TASK_URL.format(id=scrobble.logdata.todist_id)
        return url

    def subtitle_for_user(self, user_id):
        scrobble = self.scrobbles(user_id).first()
        return scrobble.logdata.title or scrobble.log.get("title")

    @classmethod
    def find_or_create(cls, title: str) -> "Task":
        task, created = cls.objects.get_or_create(title=title)
        if created:
            task.base_run_time_seconds = 1800
            task.save(update_fields=["base_run_time_seconds"])

        return task

    def scrobbles(self, user_id):
        Scrobble = apps.get_model("scrobbles", "Scrobble")
        return Scrobble.objects.filter(user_id=user_id, task=self).order_by(
            "-timestamp"
        )
