from __future__ import annotations

from datetime import timedelta

from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.db.models import Avg, Count, Q, Sum
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
from django.views import View

from core.commands.policies import (
    BP_VARIANT_KEYS,
    BP_VARIANT_META,
    ensure_variant_policies_for_profile,
)
from core.models import (
    AIRunLog,
    BusinessPlanDocument,
    BusinessPlanRevision,
    CommandAction,
    CommandChannelBinding,
    CommandProfile,
    CommandVariantPolicy,
    TranslationBridge,
    TranslationEventLog,
)
from core.translation.engine import parse_quick_mode_title

_SUPPORTED_COMMAND_CHOICES = (("bp", "Business Plan (bp)"),)
_SUPPORTED_COMMAND_SLUGS = tuple(choice[0] for choice in _SUPPORTED_COMMAND_CHOICES)


def _channel_variants(service: str, identifier: str) -> list[str]:
    value = str(identifier or "").strip()
    if not value:
        return []
    variants = [value]
    svc = str(service or "").strip().lower()
    if svc == "whatsapp":
        bare = value.split("@", 1)[0].strip()
        if bare and bare not in variants:
            variants.append(bare)
        group = f"{bare}@g.us" if bare else ""
        if group and group not in variants:
            variants.append(group)
    return variants


class CommandRoutingSettings(LoginRequiredMixin, View):
    template_name = "pages/command-routing.html"

    @staticmethod
    def _normalize_action_positions(profile):
        rows = list(profile.actions.order_by("position", "id"))
        for idx, row in enumerate(rows):
            if row.position != idx:
                row.position = idx
                row.save(update_fields=["position", "updated_at"])
        return rows

    @staticmethod
    def _redirect_with_scope(request):
        service = str(
            request.GET.get("service") or request.POST.get("service") or ""
        ).strip()
        identifier = str(
            request.GET.get("identifier") or request.POST.get("identifier") or ""
        ).strip()
        if service and identifier:
            return redirect(
                f"{reverse('command_routing')}?service={service}&identifier={identifier}"
            )
        return redirect("command_routing")

    def _context(self, request):
        profiles_qs = (
            CommandProfile.objects.filter(
                user=request.user,
                slug__in=_SUPPORTED_COMMAND_SLUGS,
            )
            .prefetch_related("channel_bindings", "actions", "variant_policies")
            .order_by("slug")
        )
        scope_service = str(request.GET.get("service") or "").strip().lower()
        scope_identifier = str(request.GET.get("identifier") or "").strip()
        scope_variants = _channel_variants(scope_service, scope_identifier)
        profiles = list(profiles_qs)
        preview_profile_id = str(request.GET.get("preview_profile_id") or "").strip()
        for profile in profiles:
            policies = ensure_variant_policies_for_profile(profile)
            profile.show_actions_editor = str(profile.slug or "").strip() != "bp"
            if str(profile.slug or "").strip() == "bp":
                keys = BP_VARIANT_KEYS
            else:
                keys = ("default",)
            profile.variant_rows = []
            for key in keys:
                row = policies.get(key)
                if row is None:
                    continue
                meta = BP_VARIANT_META.get(key, {})
                profile.variant_rows.append(
                    {
                        "variant_key": key,
                        "variant_label": str(meta.get("name") or key),
                        "trigger_token": str(
                            meta.get("trigger_token") or profile.trigger_token or ""
                        ),
                        "template_supported": bool(meta.get("template_supported")),
                        "warn_verbatim_plan": bool(
                            key in {"bp", "bp_set_range"}
                            and str(getattr(row, "generation_mode", "") or "")
                            == "verbatim"
                            and bool(getattr(row, "send_plan_to_egress", False))
                        ),
                        "row": row,
                    }
                )
            bindings = list(profile.channel_bindings.all())
            if scope_service and scope_variants:
                profile.visible_bindings = [
                    row
                    for row in bindings
                    if str(row.service or "").strip().lower() == scope_service
                    and str(row.channel_identifier or "").strip() in scope_variants
                ]
            else:
                profile.visible_bindings = bindings
            profile.enabled_egress_bindings = [
                row
                for row in bindings
                if str(row.direction or "").strip() == "egress" and bool(row.enabled)
            ]
            profile.preview_mode = (
                preview_profile_id and str(profile.id) == preview_profile_id
            )
        documents = BusinessPlanDocument.objects.filter(user=request.user).order_by(
            "-updated_at"
        )[:30]
        return {
            "profiles": profiles,
            "documents": documents,
            "channel_services": ("web", "xmpp", "signal", "whatsapp"),
            "directions": ("ingress", "egress", "scratchpad_mirror"),
            "action_types": ("extract_bp", "post_result", "save_document"),
            "command_choices": _SUPPORTED_COMMAND_CHOICES,
            "scope_service": scope_service,
            "scope_identifier": scope_identifier,
            "scope_variants": scope_variants,
            "preview_profile_id": preview_profile_id,
        }

    def get(self, request):
        return render(request, self.template_name, self._context(request))

    def post(self, request):
        action = str(request.POST.get("action") or "").strip()

        if action == "profile_create":
            slug = (
                str(
                    request.POST.get("command_slug") or request.POST.get("slug") or "bp"
                )
                .strip()
                .lower()
                or "bp"
            )
            if slug not in _SUPPORTED_COMMAND_SLUGS:
                slug = "bp"
            default_name = "Business Plan"
            default_trigger = ".bp"
            profile, _ = CommandProfile.objects.get_or_create(
                user=request.user,
                slug=slug,
                defaults={
                    "name": str(request.POST.get("name") or default_name).strip()
                    or default_name,
                    "enabled": True,
                    "trigger_token": str(
                        request.POST.get("trigger_token") or default_trigger
                    ).strip()
                    or default_trigger,
                    "template_text": str(request.POST.get("template_text") or ""),
                },
            )
            profile.name = (
                str(request.POST.get("name") or profile.name).strip() or profile.name
            )
            if slug == "bp":
                profile.trigger_token = ".bp"
                profile.template_text = str(
                    request.POST.get("template_text") or profile.template_text or ""
                )
            profile.save(
                update_fields=[
                    "name",
                    "trigger_token",
                    "template_text",
                    "reply_required",
                    "exact_match_only",
                    "updated_at",
                ]
            )
            if slug == "bp":
                CommandAction.objects.get_or_create(
                    profile=profile,
                    action_type="extract_bp",
                    defaults={"enabled": True, "position": 0},
                )
                # Keep legacy action rows in storage for compatibility and for
                # potential reuse by non-bp commands; bp UI now relies on
                # variant policies instead of exposing the generic action matrix.
                CommandAction.objects.get_or_create(
                    profile=profile,
                    action_type="save_document",
                    defaults={"enabled": True, "position": 1},
                )
                CommandAction.objects.get_or_create(
                    profile=profile,
                    action_type="post_result",
                    defaults={"enabled": True, "position": 2},
                )
                ensure_variant_policies_for_profile(profile)
            return self._redirect_with_scope(request)

        if action == "profile_update":
            profile = get_object_or_404(
                CommandProfile,
                id=request.POST.get("profile_id"),
                user=request.user,
            )
            profile.name = str(request.POST.get("name") or profile.name).strip()
            profile.enabled = bool(request.POST.get("enabled"))
            profile.trigger_token = (
                str(request.POST.get("trigger_token") or profile.trigger_token).strip()
                or ".bp"
            )
            profile.reply_required = bool(request.POST.get("reply_required"))
            profile.exact_match_only = bool(request.POST.get("exact_match_only"))
            profile.template_text = str(request.POST.get("template_text") or "")
            # Legacy field retained for compatibility only.
            profile.visibility_mode = profile.visibility_mode or "status_in_source"
            profile.save()
            ensure_variant_policies_for_profile(profile)
            return self._redirect_with_scope(request)

        if action == "profile_delete":
            profile = get_object_or_404(
                CommandProfile,
                id=request.POST.get("profile_id"),
                user=request.user,
            )
            profile.delete()
            return self._redirect_with_scope(request)

        if action == "binding_create":
            profile = get_object_or_404(
                CommandProfile,
                id=request.POST.get("profile_id"),
                user=request.user,
            )
            CommandChannelBinding.objects.create(
                profile=profile,
                direction=str(request.POST.get("direction") or "ingress").strip(),
                service=str(request.POST.get("service") or "web").strip(),
                channel_identifier=str(
                    request.POST.get("channel_identifier") or ""
                ).strip(),
                enabled=bool(request.POST.get("enabled") or "1"),
            )
            return self._redirect_with_scope(request)

        if action == "binding_delete":
            binding = get_object_or_404(
                CommandChannelBinding,
                id=request.POST.get("binding_id"),
                profile__user=request.user,
            )
            binding.delete()
            return self._redirect_with_scope(request)

        if action == "action_update":
            row = get_object_or_404(
                CommandAction,
                id=request.POST.get("command_action_id"),
                profile__user=request.user,
            )
            row.enabled = bool(request.POST.get("enabled"))
            if request.POST.get("position") not in (None, ""):
                row.position = int(request.POST.get("position") or 0)
                row.save(update_fields=["enabled", "position", "updated_at"])
            else:
                row.save(update_fields=["enabled", "updated_at"])
            return self._redirect_with_scope(request)

        if action == "action_move":
            row = get_object_or_404(
                CommandAction,
                id=request.POST.get("command_action_id"),
                profile__user=request.user,
            )
            direction = str(request.POST.get("direction") or "").strip().lower()
            if direction not in {"up", "down"}:
                return self._redirect_with_scope(request)
            with transaction.atomic():
                ordered = self._normalize_action_positions(row.profile)
                action_ids = [entry.id for entry in ordered]
                try:
                    idx = action_ids.index(row.id)
                except ValueError:
                    return self._redirect_with_scope(request)
                target_idx = idx - 1 if direction == "up" else idx + 1
                if target_idx < 0 or target_idx >= len(ordered):
                    return self._redirect_with_scope(request)
                other = ordered[target_idx]
                current_pos = ordered[idx].position
                ordered[idx].position = other.position
                other.position = current_pos
                ordered[idx].save(update_fields=["position", "updated_at"])
                other.save(update_fields=["position", "updated_at"])
            return self._redirect_with_scope(request)

        if action == "variant_policy_update":
            profile = get_object_or_404(
                CommandProfile,
                id=request.POST.get("profile_id"),
                user=request.user,
            )
            variant_key = str(request.POST.get("variant_key") or "").strip()
            policy = get_object_or_404(
                CommandVariantPolicy,
                profile=profile,
                variant_key=variant_key,
            )
            policy.enabled = bool(request.POST.get("enabled"))
            mode = (
                str(request.POST.get("generation_mode") or "verbatim").strip().lower()
            )
            policy.generation_mode = mode if mode in {"ai", "verbatim"} else "verbatim"
            policy.send_plan_to_egress = bool(request.POST.get("send_plan_to_egress"))
            policy.send_status_to_source = bool(
                request.POST.get("send_status_to_source")
            )
            policy.send_status_to_egress = bool(
                request.POST.get("send_status_to_egress")
            )
            policy.store_document = bool(request.POST.get("store_document"))
            policy.save()
            return self._redirect_with_scope(request)

        if action == "variant_policy_reset_defaults":
            profile = get_object_or_404(
                CommandProfile,
                id=request.POST.get("profile_id"),
                user=request.user,
            )
            profile.variant_policies.all().delete()
            ensure_variant_policies_for_profile(profile)
            return self._redirect_with_scope(request)

        if action == "variant_preview":
            profile = get_object_or_404(
                CommandProfile,
                id=request.POST.get("profile_id"),
                user=request.user,
            )
            ensure_variant_policies_for_profile(profile)
            service = str(
                request.GET.get("service") or request.POST.get("service") or ""
            ).strip()
            identifier = str(
                request.GET.get("identifier") or request.POST.get("identifier") or ""
            ).strip()
            query = f"?preview_profile_id={profile.id}"
            if service and identifier:
                query += f"&service={service}&identifier={identifier}"
            return redirect(f"{reverse('command_routing')}{query}")

        return self._redirect_with_scope(request)


class TranslationSettings(LoginRequiredMixin, View):
    template_name = "pages/translation-settings.html"

    def _context(self, request):
        bridges = TranslationBridge.objects.filter(user=request.user).order_by("-id")
        events = (
            TranslationEventLog.objects.filter(bridge__user=request.user)
            .select_related("bridge")
            .order_by("-created_at")[:50]
        )
        return {
            "bridges": bridges,
            "events": events,
            "channel_services": ("web", "xmpp", "signal", "whatsapp"),
            "bridge_directions": ("a_to_b", "b_to_a", "bidirectional"),
        }

    def get(self, request):
        return render(request, self.template_name, self._context(request))

    def post(self, request):
        action = str(request.POST.get("action") or "").strip()

        if action == "bridge_create":
            quick_title = str(request.POST.get("quick_mode_title") or "").strip()
            inferred = parse_quick_mode_title(quick_title)
            TranslationBridge.objects.create(
                user=request.user,
                name=str(request.POST.get("name") or "Translation Bridge").strip()
                or "Translation Bridge",
                enabled=bool(request.POST.get("enabled") or "1"),
                a_service=str(request.POST.get("a_service") or "web").strip(),
                a_channel_identifier=str(
                    request.POST.get("a_channel_identifier") or ""
                ).strip(),
                a_language=str(
                    request.POST.get("a_language") or inferred.get("a_language") or "en"
                ).strip(),
                b_service=str(request.POST.get("b_service") or "web").strip(),
                b_channel_identifier=str(
                    request.POST.get("b_channel_identifier") or ""
                ).strip(),
                b_language=str(
                    request.POST.get("b_language") or inferred.get("b_language") or "en"
                ).strip(),
                direction=str(request.POST.get("direction") or "bidirectional").strip(),
                quick_mode_title=quick_title,
                settings={},
            )
            return redirect("translation_settings")

        if action == "bridge_delete":
            bridge = get_object_or_404(
                TranslationBridge, id=request.POST.get("bridge_id"), user=request.user
            )
            bridge.delete()
            return redirect("translation_settings")

        return redirect("translation_settings")


class AIExecutionLogSettings(LoginRequiredMixin, View):
    template_name = "pages/ai-execution-log.html"

    def _context(self, request):
        now = timezone.now()
        runs_qs = AIRunLog.objects.filter(user=request.user)
        runs = runs_qs.order_by("-started_at")[:300]
        last_24h = runs_qs.filter(started_at__gte=now - timedelta(hours=24))
        last_7d = runs_qs.filter(started_at__gte=now - timedelta(days=7))

        total_runs = runs_qs.count()
        total_ok = runs_qs.filter(status="ok").count()
        total_failed = runs_qs.filter(status="failed").count()
        avg_ms = runs_qs.aggregate(v=Avg("duration_ms")).get("v") or 0
        success_rate = (
            (float(total_ok) / float(total_runs) * 100.0) if total_runs else 0.0
        )

        usage_totals = runs_qs.aggregate(
            prompt_chars_total=Sum("prompt_chars"),
            response_chars_total=Sum("response_chars"),
            avg_prompt_chars=Avg("prompt_chars"),
            avg_response_chars=Avg("response_chars"),
        )

        stats = {
            "total_runs": total_runs,
            "total_ok": total_ok,
            "total_failed": total_failed,
            "last_24h_runs": last_24h.count(),
            "last_24h_failed": last_24h.filter(status="failed").count(),
            "last_7d_runs": last_7d.count(),
            "avg_duration_ms": int(avg_ms),
            "success_rate": round(success_rate, 1),
            "total_prompt_chars": int(usage_totals.get("prompt_chars_total") or 0),
            "total_response_chars": int(usage_totals.get("response_chars_total") or 0),
            "avg_prompt_chars": int(usage_totals.get("avg_prompt_chars") or 0),
            "avg_response_chars": int(usage_totals.get("avg_response_chars") or 0),
        }

        operation_breakdown = (
            runs_qs.values("operation")
            .annotate(
                total=Count("id"),
                failed=Count("id", filter=Q(status="failed")),
                ok=Count("id", filter=Q(status="ok")),
            )
            .order_by("-total", "operation")[:20]
        )
        model_breakdown = (
            runs_qs.values("model")
            .annotate(
                total=Count("id"),
                failed=Count("id", filter=Q(status="failed")),
                ok=Count("id", filter=Q(status="ok")),
            )
            .order_by("-total", "model")[:20]
        )

        return {
            "stats": stats,
            "runs": runs,
            "operation_breakdown": operation_breakdown,
            "model_breakdown": model_breakdown,
        }

    def get(self, request):
        return render(request, self.template_name, self._context(request))


class AIExecutionRunDetailView(LoginRequiredMixin, View):
    template_name = "partials/ai-execution-run-detail.html"

    def get(self, request, run_id):
        run = get_object_or_404(AIRunLog, id=run_id, user=request.user)
        return render(request, self.template_name, {"run": run})


class AIExecutionRunDetailTabView(LoginRequiredMixin, View):
    template_name = "partials/ai-execution-run-detail-tab.html"

    def get(self, request, run_id, tab_slug):
        run = get_object_or_404(AIRunLog, id=run_id, user=request.user)
        slug = str(tab_slug or "").strip().lower()
        if slug not in {"error"}:
            return JsonResponse({"ok": False, "error": "unknown_tab"}, status=404)
        return render(
            request,
            self.template_name,
            {
                "run": run,
                "tab_slug": slug,
            },
        )


class BusinessPlanInbox(LoginRequiredMixin, View):
    template_name = "pages/business-plan-inbox.html"

    def get(self, request):
        status_filter = str(request.GET.get("status") or "").strip().lower()
        service_filter = str(request.GET.get("service") or "").strip().lower()
        query = str(request.GET.get("q") or "").strip()

        rows = (
            BusinessPlanDocument.objects.filter(user=request.user)
            .select_related("command_profile")
            .annotate(revision_count=Count("revisions"))
            .order_by("-updated_at")
        )
        if status_filter in {"draft", "final"}:
            rows = rows.filter(status=status_filter)
        if service_filter in {"xmpp", "whatsapp", "signal", "instagram", "web"}:
            rows = rows.filter(source_service=service_filter)
        if query:
            rows = rows.filter(
                Q(title__icontains=query)
                | Q(source_channel_identifier__icontains=query)
                | Q(command_profile__name__icontains=query)
            )

        stats = BusinessPlanDocument.objects.filter(user=request.user).aggregate(
            total=Count("id"),
            draft=Count("id", filter=Q(status="draft")),
            final=Count("id", filter=Q(status="final")),
        )

        return render(
            request,
            self.template_name,
            {
                "documents": rows[:250],
                "filters": {
                    "status": status_filter,
                    "service": service_filter,
                    "q": query,
                },
                "stats": stats,
                "service_choices": ("xmpp", "whatsapp", "signal", "instagram", "web"),
            },
        )


class BusinessPlanEditor(LoginRequiredMixin, View):
    template_name = "pages/business-plan-editor.html"

    def get(self, request, doc_id):
        document = get_object_or_404(BusinessPlanDocument, id=doc_id, user=request.user)
        revisions = document.revisions.order_by("-created_at")[:100]
        return render(
            request,
            self.template_name,
            {
                "document": document,
                "revisions": revisions,
            },
        )

    def post(self, request, doc_id):
        document = get_object_or_404(BusinessPlanDocument, id=doc_id, user=request.user)
        document.title = str(request.POST.get("title") or document.title).strip()
        document.status = str(request.POST.get("status") or document.status).strip()
        document.content_markdown = str(request.POST.get("content_markdown") or "")
        document.save()
        BusinessPlanRevision.objects.create(
            document=document,
            editor_user=request.user,
            content_markdown=document.content_markdown,
            structured_payload=document.structured_payload or {},
        )
        return redirect("business_plan_editor", doc_id=str(document.id))


class TranslationPreview(LoginRequiredMixin, View):
    def post(self, request):
        bridge = get_object_or_404(
            TranslationBridge,
            id=request.POST.get("bridge_id"),
            user=request.user,
        )
        source = str(request.POST.get("source") or "").strip().lower()
        text = str(request.POST.get("text") or "")
        if source == "a":
            target_language = bridge.b_language
        else:
            target_language = bridge.a_language
        return JsonResponse(
            {
                "ok": True,
                "bridge_id": str(bridge.id),
                "target_language": target_language,
                "preview": text,
                "note": "Preview endpoint is non-mutating; final translation occurs in runtime sync.",
            }
        )
