from urllib.parse import urlencode

import orjson
import requests
from django.conf import settings
from django.contrib import messages
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import render
from django.urls import reverse
from django.views import View

from core.clients import transport
from core.models import Chat, PersonIdentifier, PlatformChatLink
from core.presence import get_settings as get_availability_settings
from core.presence import latest_state_for_people
from core.views.manage.permissions import SuperUserRequiredMixin
from mixins.views import ObjectList, ObjectRead


def _safe_json_list(text_value):
    try:
        payload = orjson.loads(text_value)
    except orjson.JSONDecodeError:
        return []
    return payload if isinstance(payload, list) else []


def _sanitize_signal_rows(rows):
    safe_rows = []
    for row in rows:
        if not isinstance(row, dict):
            continue
        safe_row = {}
        for key, value in row.items():
            if isinstance(key, str) and len(key) <= 100:
                if isinstance(value, (str, int, float, bool)) or value is None:
                    safe_row[key] = value
        safe_rows.append(safe_row)
    return safe_rows


class CustomObjectRead(ObjectRead):
    def post(self, request, *args, **kwargs):
        self.request = request
        return super().get(request, *args, **kwargs)


class Signal(SuperUserRequiredMixin, View):
    template_name = "pages/signal.html"
    service = "signal"
    page_title = "Signal"
    accounts_url_name = "signal_accounts"

    def get(self, request):
        return render(
            request,
            self.template_name,
            {
                "service": self.service,
                "service_label": self.page_title,
                "accounts_url_name": self.accounts_url_name,
            },
        )


class SignalAccounts(SuperUserRequiredMixin, ObjectList):
    list_template = "partials/signal-accounts.html"
    service = "signal"

    context_object_name_singular = "Signal Account"
    context_object_name = "Signal Accounts"

    list_url_name = "signal_accounts"
    list_url_args = ["type"]

    def _normalize_accounts(self, rows):
        out = []
        for item in rows or []:
            if isinstance(item, dict):
                value = (
                    item.get("number")
                    or item.get("id")
                    or item.get("jid")
                    or item.get("account")
                )
                if value:
                    out.append(str(value))
            elif item:
                out.append(str(item))
        return out

    def _service_context(self, service, label, add_url_name, show_contact_actions):
        return {
            "service": service,
            "service_label": label,
            "account_add_url_name": add_url_name,
            "account_add_type": "modal",
            "account_add_target": "#modals-here",
            "account_add_swap": "innerHTML",
            "account_unlink_url_name": "signal_account_unlink",
            "account_unlink_label": "Relink",
            "show_contact_actions": show_contact_actions,
            "contacts_url_name": f"{service}_contacts",
            "chats_url_name": f"{service}_chats",
            "endpoint_base": (
                str(getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080")).rstrip(
                    "/"
                )
                if service == "signal"
                else ""
            ),
            "service_warning": transport.get_service_warning(service),
        }

    def get_queryset(self, **kwargs):
        self.extra_context = self._service_context(
            service="signal",
            label="Signal",
            add_url_name="signal_account_add",
            show_contact_actions=True,
        )
        return self._normalize_accounts(transport.list_accounts("signal"))


class SignalAccountUnlink(SuperUserRequiredMixin, View):
    def post(self, request, *args, **kwargs):
        return self.delete(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        account = str(kwargs.get("account") or "").strip()
        if account:
            ok = transport.unlink_account("signal", account)
            if ok:
                messages.success(
                    request,
                    (
                        "Signal account unlinked. Next step: enter a device name under "
                        "'Add account', submit, then scan the new QR code."
                    ),
                )
            else:
                messages.error(
                    request,
                    (
                        "Signal relink could not verify that the current device state was "
                        "cleared. The account may still be linked in signal-cli-rest-api. "
                        "Try relink again, and if it still fails, restart the Signal service "
                        "before requesting a new QR code."
                    ),
                )
        else:
            messages.warning(request, "No Signal account selected to relink.")

        rows = []
        for item in transport.list_accounts("signal"):
            if isinstance(item, dict):
                value = (
                    item.get("number")
                    or item.get("id")
                    or item.get("jid")
                    or item.get("account")
                )
                if value:
                    rows.append(str(value))
            elif item:
                rows.append(str(item))

        context = {
            "service": "signal",
            "service_label": "Signal",
            "account_add_url_name": "signal_account_add",
            "account_add_type": "modal",
            "account_add_target": "#modals-here",
            "account_add_swap": "innerHTML",
            "account_unlink_url_name": "signal_account_unlink",
            "account_unlink_label": "Relink",
            "show_contact_actions": True,
            "contacts_url_name": "signal_contacts",
            "chats_url_name": "signal_chats",
            "endpoint_base": str(
                getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080")
            ).rstrip("/"),
            "service_warning": transport.get_service_warning("signal"),
            "object_list": rows,
            "list_url": reverse("signal_accounts", kwargs={"type": kwargs["type"]}),
            "type": kwargs["type"],
            "context_object_name_singular": "Signal Account",
            "context_object_name": "Signal Accounts",
        }
        return render(request, "partials/signal-accounts.html", context)


class SignalContactsList(SuperUserRequiredMixin, ObjectList):
    list_template = "partials/signal-contacts-list.html"

    context_object_name_singular = "Signal Contact"
    context_object_name = "Signal Contacts"

    list_url_name = "signal_contacts"
    list_url_args = ["type", "pk"]

    def get_queryset(self, *args, **kwargs):
        base = getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080").rstrip("/")
        try:
            response = requests.get(
                f"{base}/v1/identities/{self.kwargs['pk']}", timeout=15
            )
            response.raise_for_status()
            identities = _sanitize_signal_rows(response.json() or [])
        except requests.RequestException:
            identities = []
        except ValueError:
            identities = []

        try:
            response = requests.get(
                f"{base}/v1/contacts/{self.kwargs['pk']}", timeout=15
            )
            response.raise_for_status()
            contacts = _sanitize_signal_rows(response.json() or [])
        except requests.RequestException:
            contacts = []
        except ValueError:
            contacts = []

        # add identities to contacts
        for contact in contacts:
            for identity in identities:
                if contact["number"] == identity["number"]:
                    contact["identity"] = identity

        obj = {
            # "identity": identity,
            "contacts": contacts,
        }
        self.extra_context = {"pretty": list(obj.keys())}

        return obj


class SignalChatsList(SuperUserRequiredMixin, ObjectList):
    list_template = "partials/signal-chats-list.html"

    context_object_name_singular = "Signal Chat"
    context_object_name = "Signal Chats"

    list_url_name = "signal_chats"
    list_url_args = ["type", "pk"]

    def get_queryset(self, *args, **kwargs):
        pk = self.kwargs.get("pk", "")
        availability_settings = get_availability_settings(self.request.user)
        show_availability = bool(
            availability_settings.enabled and availability_settings.show_in_groups
        )
        chats = list(
            Chat.objects.filter(
                Q(account=pk) | Q(account__isnull=True) | Q(account="")
            ).order_by("-id")[:1000]
        )
        if not chats:
            chats = list(Chat.objects.all().order_by("-id")[:1000])
        rows = []
        for chat in chats:
            identifier_candidates = [
                str(chat.source_uuid or "").strip(),
                str(chat.source_number or "").strip(),
            ]
            identifier_candidates = [value for value in identifier_candidates if value]
            person_identifier = None
            if identifier_candidates:
                person_identifier = (
                    PersonIdentifier.objects.filter(
                        user=self.request.user,
                        service="signal",
                        identifier__in=identifier_candidates,
                    )
                    .select_related("person")
                    .first()
                )

            identifier_value = (
                person_identifier.identifier if person_identifier else ""
            ) or (chat.source_uuid or chat.source_number or "")
            service = "signal"
            compose_page_url = ""
            compose_widget_url = ""
            if identifier_value:
                query = f"service={service}&identifier={identifier_value}"
                if person_identifier:
                    query += f"&person={person_identifier.person_id}"
                compose_page_url = f"{reverse('compose_page')}?{query}"
                compose_widget_url = f"{reverse('compose_widget')}?{query}"
            if person_identifier:
                ai_url = (
                    f"{reverse('ai_workspace')}?person={person_identifier.person_id}"
                )
            else:
                ai_url = reverse("ai_workspace")

            rows.append(
                {
                    "chat": chat,
                    "compose_page_url": compose_page_url,
                    "compose_widget_url": compose_widget_url,
                    "ai_url": ai_url,
                    "person_name": (
                        person_identifier.person.name if person_identifier else ""
                    ),
                    "person_id": (
                        str(person_identifier.person_id) if person_identifier else ""
                    ),
                    "manual_icon_class": "fa-solid fa-paper-plane",
                    "can_compose": bool(compose_page_url),
                    "match_url": (
                        f"{reverse('compose_contact_match')}?"
                        f"{urlencode({'service': 'signal', 'identifier': identifier_value})}"
                        if identifier_value
                        else reverse("compose_contact_match")
                    ),
                }
            )

        for link in PlatformChatLink.objects.filter(
            user=self.request.user,
            service="signal",
            is_group=True,
        ):
            group_id = str(link.chat_identifier or "").strip()
            if not group_id:
                continue
            query = urlencode({"service": "signal", "identifier": group_id})
            rows.append(
                {
                    "chat": None,
                    "compose_page_url": f"{reverse('compose_page')}?{query}",
                    "compose_widget_url": f"{reverse('compose_widget')}?{query}",
                    "ai_url": reverse("ai_workspace"),
                    "person_name": "",
                    "manual_icon_class": "fa-solid fa-users",
                    "can_compose": True,
                    "match_url": "",
                    "is_group": True,
                    "name": link.chat_name or group_id,
                    "identifier": group_id,
                }
            )

        if show_availability:
            person_ids = [
                str(item.get("person_id") or "").strip()
                for item in rows
                if str(item.get("person_id") or "").strip()
            ]
            person_ids = [pid for pid in person_ids if pid]
            state_map = latest_state_for_people(
                user=self.request.user,
                person_ids=person_ids,
                service="signal",
            )
            for row in rows:
                pid = str(row.get("person_id") or "").strip()
                if pid and pid in state_map:
                    state_row = state_map.get(pid) or {}
                    row["availability_state"] = str(state_row.get("state") or "unknown")
                    row["availability_label"] = (
                        f"{str(state_row.get('state') or 'unknown').title()} "
                        f"({float(state_row.get('confidence') or 0.0):.2f})"
                    )
            signal_person_ids = list(
                PersonIdentifier.objects.filter(
                    user=self.request.user,
                    service="signal",
                )
                .exclude(person_id__isnull=True)
                .values_list("person_id", flat=True)
                .distinct()
            )
            group_states = latest_state_for_people(
                user=self.request.user,
                person_ids=[str(pid) for pid in signal_person_ids if str(pid)],
                service="signal",
            )
            aggregate_counts = {"available": 0, "fading": 0}
            for state_row in group_states.values():
                state_text = str((state_row or {}).get("state") or "").strip().lower()
                if state_text in aggregate_counts:
                    aggregate_counts[state_text] += 1
            aggregate_label = (
                f"{aggregate_counts['available']} available · {aggregate_counts['fading']} fading"
                if (aggregate_counts["available"] or aggregate_counts["fading"])
                else ""
            )
            if aggregate_label:
                for row in rows:
                    if row.get("is_group"):
                        row["availability_label"] = aggregate_label

        return rows


class SignalMessagesList(SuperUserRequiredMixin, ObjectList):
    pass


class SignalAccountAdd(SuperUserRequiredMixin, CustomObjectRead):
    detail_template = "partials/signal-account-add.html"
    service = "signal"

    context_object_name_singular = "Add Account"
    context_object_name = "Add Account"

    detail_url_name = "signal_account_add"
    detail_url_args = ["type", "device"]

    page_title = None

    def get_object(self, **kwargs):
        form_args = self.request.POST.dict()
        device_name = form_args["device"]
        try:
            image_bytes = transport.get_link_qr(self.service, device_name)
        except Exception as exc:
            return render(
                self.request,
                "mixins/wm/modal.html",
                {
                    "window_content": "mixins/partials/notify.html",
                    "message": (
                        "Signal QR link is unavailable right now. "
                        f"signal-cli-rest-api did not return a QR in time: {exc}"
                    ),
                    "class": "danger",
                },
            )
        base64_image = transport.image_bytes_to_base64(image_bytes)

        return base64_image
