from datetime import timedelta
from pathlib import Path

from django.contrib import messages
from django.contrib.auth import logout, update_session_auth_hash
from django.contrib.auth.views import LoginView
from django.db.models import Q, Sum
from django.http import FileResponse, Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.utils import timezone
from django.views.decorators.http import require_POST

from billing.models import Payment, Renewal
from core.models import Document, PortalConfiguration
from services.models import Service
from support.models import SupportMessage, SupportTicket

from .decorators import client_portal_required, portal_access_for
from .forms import (ClientProfileForm, PaymentProofForm, PortalAuthenticationForm, PortalPasswordChangeForm, TicketCreateForm, TicketReplyForm)


class PortalLoginView(LoginView):
    template_name = "client_portal/login.html"
    authentication_form = PortalAuthenticationForm
    redirect_authenticated_user = False

    def dispatch(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            access = portal_access_for(request.user)
            if access:
                return redirect("client_portal:dashboard")
            if request.user.is_staff:
                return redirect("admin:index")
            logout(request)
        return super().dispatch(request, *args, **kwargs)

    def get_success_url(self):
        if self.request.user.is_staff or self.request.user.is_superuser:
            return str(reverse_lazy("admin:index"))
        access = portal_access_for(self.request.user)
        if access:
            access.last_portal_login_at = timezone.now()
            if not access.accepted_at:
                access.accepted_at = timezone.now()
            access.save(update_fields=["last_portal_login_at", "accepted_at", "updated_at"])
            return str(reverse_lazy("client_portal:dashboard"))
        return str(reverse_lazy("home"))


@require_POST
def portal_logout(request):
    logout(request)
    return redirect("security:login")


def _base_context(request, **extra):
    client = request.portal_client
    access = request.portal_access
    open_statuses = [
        SupportTicket.Status.OPEN,
        SupportTicket.Status.IN_PROGRESS,
        SupportTicket.Status.REOPENED,
        SupportTicket.Status.WAITING_CLIENT,
        SupportTicket.Status.WAITING_INTERNAL,
    ]
    context = {
        "portal_client": client,
        "portal_access": access,
        "portal_actor_user": getattr(request, "portal_actor_user", access.user),
        "portal_user_profile": __import__("security.services", fromlist=["get_profile"]).get_profile(access.user),
        "portal_preview_mode": getattr(request, "portal_preview_mode", False),
        "portal_preview_interactive": getattr(request, "portal_preview_interactive", False),
        "open_ticket_count": client.support_tickets.filter(client_visible=True, status__in=open_statuses).count(),
        "unread_ticket_count": SupportMessage.objects.filter(
            ticket__client=client,
            ticket__client_visible=True,
            author_type=SupportMessage.AuthorType.STAFF,
            is_internal=False,
            read_by_client_at__isnull=True,
        ).count(),
        "company": PortalConfiguration.objects.order_by("pk").first(),
    }
    # During an administrative preview the global context processor sees the
    # staff user. Build the client-facing notification counters explicitly.
    if context["portal_preview_mode"]:
        from monitoring.models import MonitorIncident
        today = timezone.localdate()
        unread = context["unread_ticket_count"]
        renewals = Renewal.objects.filter(
            service__client=client,
            due_date__range=(today, today + timedelta(days=30)),
        ).exclude(status=Renewal.Status.CANCELLED).count()
        incidents = MonitorIncident.objects.filter(
            monitor__technical_asset__service__client=client,
            client_visible=True,
            status__in=[MonitorIncident.Status.OPEN, MonitorIncident.Status.ACKNOWLEDGED],
        ).count()
        items = [
            {"title": "Novas respostas de suporte", "count": unread, "url": reverse_lazy("client_portal:tickets"), "icon": "forum"},
            {"title": "Renovações nos próximos 30 dias", "count": renewals, "url": reverse_lazy("client_portal:renewals"), "icon": "event_repeat"},
            {"title": "Incidentes dos websites", "count": incidents, "url": reverse_lazy("client_portal:monitoring"), "icon": "monitor_heart"},
        ]
        if access.can_view_financial:
            pending = Payment.objects.filter(renewal__service__client=client).exclude(
                status__in=[Payment.Status.PAID, Payment.Status.CANCELLED, Payment.Status.REFUNDED]
            ).count()
            items.append({"title": "Pagamentos pendentes", "count": pending, "url": reverse_lazy("client_portal:payments"), "icon": "payments"})
        context["k4w_client_notifications"] = items
        context["k4w_notification_count"] = sum(item["count"] for item in items)
    context.update(extra)
    return context


@client_portal_required
def dashboard(request):
    client = request.portal_client
    today = timezone.localdate()
    services = client.services.exclude(status__in=["cancelled", "archived"]).order_by("next_renewal_date")
    renewals = Renewal.objects.filter(service__client=client).exclude(status=Renewal.Status.CANCELLED)
    payments = Payment.objects.filter(renewal__service__client=client)
    outstanding = payments.exclude(status__in=[Payment.Status.PAID, Payment.Status.CANCELLED, Payment.Status.REFUNDED]).aggregate(
        total=Sum("amount")
    )["total"] or 0
    next_renewals = renewals.filter(due_date__gte=today).order_by("due_date")[:5]
    tickets = client.support_tickets.filter(client_visible=True).order_by("-last_activity_at")[:5]
    documents = client.documents.filter(visible_to_client=True).exclude(file="").order_by("-created_at")[:5]
    from monitoring.models import MonitorIncident, WebsiteMonitor
    monitors = WebsiteMonitor.objects.filter(
        technical_asset__service__client=client,
        client_visible=True,
    ).select_related("technical_asset__service").order_by("status", "name")
    monitor_incidents = MonitorIncident.objects.filter(
        monitor__technical_asset__service__client=client,
        client_visible=True,
        status__in=[MonitorIncident.Status.OPEN, MonitorIncident.Status.ACKNOWLEDGED],
    ).count()
    return render(
        request,
        "client_portal/dashboard.html",
        _base_context(
            request,
            page_title="Visão geral",
            active_nav="dashboard",
            services_count=services.count(),
            upcoming_count=renewals.filter(due_date__range=(today, today + timedelta(days=30))).count(),
            outstanding=outstanding,
            documents_count=client.documents.filter(visible_to_client=True).exclude(file="").count(),
            next_renewals=next_renewals,
            recent_tickets=tickets,
            recent_documents=documents,
            monitor_count=monitors.count(),
            monitor_down_count=monitors.filter(status=WebsiteMonitor.Status.DOWN).count(),
            monitor_incidents=monitor_incidents,
            monitored_services=monitors[:5],
        ),
    )


@client_portal_required
def service_list(request):
    services = request.portal_client.services.select_related("provider").order_by("name")
    return render(request, "client_portal/services.html", _base_context(request, page_title="Os meus serviços", active_nav="services", services=services))


@client_portal_required
def service_detail(request, pk):
    service = get_object_or_404(Service, pk=pk, client=request.portal_client)
    renewals = service.renewals.order_by("-due_date")[:12]
    documents = service.documents.filter(visible_to_client=True).exclude(file="")
    from monitoring.models import WebsiteMonitor
    monitors = WebsiteMonitor.objects.filter(
        technical_asset__service=service,
        client_visible=True,
    ).prefetch_related("checks", "incidents").order_by("name")
    return render(request, "client_portal/service_detail.html", _base_context(
        request, page_title=service.name, active_nav="services", service=service,
        renewals=renewals, documents=documents, monitors=monitors,
    ))


@client_portal_required
def monitoring_status(request):
    from monitoring.models import MonitorCheck, MonitorIncident, WebsiteMonitor

    monitors = WebsiteMonitor.objects.filter(
        technical_asset__service__client=request.portal_client,
        client_visible=True,
    ).select_related("technical_asset__service").order_by("status", "name")
    incidents = MonitorIncident.objects.filter(
        monitor__technical_asset__service__client=request.portal_client,
        client_visible=True,
    ).select_related("monitor", "monitor__technical_asset__service").order_by("status", "-opened_at")[:30]
    recent_checks = MonitorCheck.objects.filter(
        monitor__technical_asset__service__client=request.portal_client,
        monitor__client_visible=True,
    ).select_related("monitor").order_by("-checked_at")[:20]
    return render(request, "client_portal/monitoring.html", _base_context(
        request,
        page_title="Estado dos websites",
        active_nav="monitoring",
        monitors=monitors,
        incidents=incidents,
        recent_checks=recent_checks,
        monitor_up=monitors.filter(status=WebsiteMonitor.Status.UP).count(),
        monitor_attention=monitors.filter(status__in=[WebsiteMonitor.Status.DEGRADED, WebsiteMonitor.Status.DOWN]).count(),
    ))


@client_portal_required
def renewal_list(request):
    renewals = Renewal.objects.filter(service__client=request.portal_client).select_related("service").order_by("-due_date")
    return render(request, "client_portal/renewals.html", _base_context(request, page_title="Renovações", active_nav="renewals", renewals=renewals))


@client_portal_required
def payment_list(request):
    if not request.portal_access.can_view_financial:
        messages.error(request, "O seu perfil não tem acesso à informação financeira.")
        return redirect("client_portal:dashboard")
    payments = Payment.objects.filter(renewal__service__client=request.portal_client).select_related("renewal__service").order_by("-requested_at")
    return render(request, "client_portal/payments.html", _base_context(request, page_title="Pagamentos", active_nav="payments", payments=payments))


@client_portal_required
@require_POST
def payment_proof(request, pk):
    if not request.portal_access.can_view_financial:
        raise Http404
    payment = get_object_or_404(Payment, pk=pk, renewal__service__client=request.portal_client)
    form = PaymentProofForm(request.POST, request.FILES, instance=payment)
    if form.is_valid():
        payment = form.save(commit=False)
        if payment.status in {Payment.Status.PENDING, Payment.Status.WAITING_PROOF, Payment.Status.REJECTED}:
            payment.status = Payment.Status.VALIDATING
        payment.save()
        messages.success(request, "Comprovativo enviado. O pagamento ficou em validação.")
    else:
        messages.error(request, "Não foi possível carregar o comprovativo. Confirme o ficheiro selecionado.")
    return redirect("client_portal:payments")


@client_portal_required
def document_list(request):
    if not request.portal_access.can_view_documents:
        messages.error(request, "O seu perfil não tem acesso aos documentos.")
        return redirect("client_portal:dashboard")
    documents = Document.objects.filter(visible_to_client=True).filter(
        Q(client=request.portal_client) | Q(service__client=request.portal_client) | Q(payment__renewal__service__client=request.portal_client) | Q(support_ticket__client=request.portal_client)
    ).exclude(file="").distinct().order_by("-issued_on", "-created_at")
    return render(request, "client_portal/documents.html", _base_context(request, page_title="Documentos", active_nav="documents", documents=documents))


@client_portal_required
def document_download(request, pk):
    if not request.portal_access.can_view_documents:
        raise Http404
    document = get_object_or_404(Document, pk=pk, visible_to_client=True)
    belongs = document.client_id == request.portal_client.id
    belongs = belongs or (document.service_id and document.service.client_id == request.portal_client.id)
    belongs = belongs or (document.payment_id and document.payment.client.id == request.portal_client.id)
    belongs = belongs or (document.support_ticket_id and document.support_ticket.client_id == request.portal_client.id)
    if not belongs or not document.file:
        raise Http404
    try:
        return FileResponse(document.file.open("rb"), as_attachment=True, filename=Path(document.file.name).name)
    except FileNotFoundError as exc:
        raise Http404 from exc


@client_portal_required
def ticket_list(request):
    tickets = request.portal_client.support_tickets.filter(client_visible=True).select_related("service", "assigned_to").order_by("-last_activity_at")
    return render(request, "client_portal/tickets.html", _base_context(request, page_title="Suporte", active_nav="tickets", tickets=tickets))


@client_portal_required
def ticket_create(request):
    if not request.portal_access.can_manage_tickets:
        messages.error(request, "O seu perfil não permite criar tickets.")
        return redirect("client_portal:tickets")
    if request.method == "POST":
        form = TicketCreateForm(request.POST, request.FILES, client=request.portal_client)
        if form.is_valid():
            ticket = form.save(commit=False)
            ticket.client = request.portal_client
            ticket.channel = SupportTicket.Channel.PORTAL
            ticket.status = SupportTicket.Status.OPEN
            ticket.client_visible = True
            ticket.save()
            SupportMessage.objects.create(
                ticket=ticket,
                author_type=SupportMessage.AuthorType.CLIENT,
                author_name=request.portal_access.display_name,
                author_email=request.portal_actor_user.email or request.portal_client.email,
                message=ticket.description,
                attachment=form.cleaned_data.get("attachment"),
                read_by_client_at=timezone.now(),
            )
            messages.success(request, f"Ticket {ticket.reference} criado com sucesso.")
            return redirect("client_portal:ticket_detail", pk=ticket.pk)
    else:
        form = TicketCreateForm(client=request.portal_client)
    return render(request, "client_portal/ticket_form.html", _base_context(request, page_title="Novo ticket", active_nav="tickets", form=form))


@client_portal_required
def ticket_detail(request, pk):
    ticket = get_object_or_404(SupportTicket, pk=pk, client=request.portal_client, client_visible=True)
    visible_messages = ticket.messages.filter(is_internal=False).select_related("staff_user")
    now = timezone.now()
    visible_messages.filter(author_type=SupportMessage.AuthorType.STAFF, read_by_client_at__isnull=True).update(read_by_client_at=now)
    ticket.client_last_read_at = now
    ticket.save(update_fields=["client_last_read_at", "updated_at"])

    if request.method == "POST":
        if not request.portal_access.can_manage_tickets:
            messages.error(request, "O seu perfil não permite responder a tickets.")
            return redirect("client_portal:ticket_detail", pk=pk)
        form = TicketReplyForm(request.POST, request.FILES)
        if form.is_valid():
            reply = form.save(commit=False)
            reply.ticket = ticket
            reply.author_type = SupportMessage.AuthorType.CLIENT
            reply.author_name = request.portal_access.display_name
            reply.author_email = request.portal_actor_user.email or request.portal_client.email
            reply.read_by_client_at = now
            reply.save()
            messages.success(request, "Resposta enviada à equipa Kreate4Web.")
            return redirect("client_portal:ticket_detail", pk=ticket.pk)
    else:
        form = TicketReplyForm()
    return render(request, "client_portal/ticket_detail.html", _base_context(request, page_title=ticket.reference, active_nav="tickets", ticket=ticket, ticket_messages=visible_messages, form=form))


@client_portal_required
@require_POST
def ticket_resolve(request, pk):
    ticket = get_object_or_404(SupportTicket, pk=pk, client=request.portal_client, client_visible=True)
    ticket.last_activity_at = timezone.now()
    ticket.mark_resolved(by_client=True, note="Marcado como resolvido pelo cliente no portal.")
    SupportMessage.objects.create(
        ticket=ticket,
        author_type=SupportMessage.AuthorType.SYSTEM,
        author_name="Portal Kreate4Web",
        message="O cliente marcou este ticket como resolvido.",
        read_by_client_at=timezone.now(),
    )
    messages.success(request, "O ticket foi marcado como resolvido.")
    return redirect("client_portal:ticket_detail", pk=ticket.pk)


@client_portal_required
@require_POST
def ticket_reopen(request, pk):
    ticket = get_object_or_404(SupportTicket, pk=pk, client=request.portal_client, client_visible=True)
    ticket.reopen()
    SupportMessage.objects.create(
        ticket=ticket,
        author_type=SupportMessage.AuthorType.SYSTEM,
        author_name="Portal Kreate4Web",
        message="O cliente reabriu este ticket.",
        read_by_client_at=timezone.now(),
    )
    messages.success(request, "O ticket foi reaberto.")
    return redirect("client_portal:ticket_detail", pk=ticket.pk)


@client_portal_required
def profile(request):
    can_edit_company = request.portal_access.role == request.portal_access.Role.OWNER and (not request.portal_preview_mode or request.portal_preview_interactive)
    profile_form = ClientProfileForm(instance=request.portal_client, prefix="profile")
    password_form = PortalPasswordChangeForm(request.portal_actor_user, prefix="password")

    if request.method == "POST":
        action = request.POST.get("action")
        if action == "profile":
            if not can_edit_company:
                messages.error(request, "Apenas o responsável principal pode alterar os contactos da entidade.")
            else:
                profile_form = ClientProfileForm(request.POST, instance=request.portal_client, prefix="profile")
                if profile_form.is_valid():
                    profile_form.save()
                    messages.success(request, "Dados de contacto atualizados.")
                    return redirect("client_portal:profile")
        elif action == "password":
            if getattr(request, "portal_preview_mode", False):
                messages.error(request, "A palavra-passe do cliente não pode ser alterada durante uma pré-visualização administrativa.")
                return redirect("client_portal:profile")
            password_form = PortalPasswordChangeForm(request.portal_actor_user, request.POST, prefix="password")
            if password_form.is_valid():
                user = password_form.save()
                update_session_auth_hash(request, user)
                messages.success(request, "Palavra-passe alterada com sucesso.")
                return redirect("client_portal:profile")

    return render(
        request,
        "client_portal/profile.html",
        _base_context(
            request,
            page_title="Perfil e contactos",
            active_nav="profile",
            profile_form=profile_form,
            password_form=password_form,
            can_edit_company=can_edit_company,
        ),
    )


@client_portal_required
def ticket_attachment(request, pk):
    message = get_object_or_404(SupportMessage, pk=pk, ticket__client=request.portal_client, ticket__client_visible=True, is_internal=False)
    if not message.attachment:
        raise Http404
    try:
        return FileResponse(message.attachment.open("rb"), as_attachment=True, filename=Path(message.attachment.name).name)
    except FileNotFoundError as exc:
        raise Http404 from exc


@client_portal_required
def notification_center(request):
    client = request.portal_client
    today = timezone.localdate()
    unread_messages = SupportMessage.objects.filter(
        ticket__client=client,
        ticket__client_visible=True,
        author_type=SupportMessage.AuthorType.STAFF,
        is_internal=False,
        read_by_client_at__isnull=True,
    ).select_related("ticket").order_by("-created_at")
    upcoming_renewals = Renewal.objects.filter(
        service__client=client,
        due_date__range=(today, today + timedelta(days=30)),
    ).exclude(status=Renewal.Status.CANCELLED).select_related("service").order_by("due_date")
    pending_payments = Payment.objects.none()
    if request.portal_access.can_view_financial:
        pending_payments = Payment.objects.filter(renewal__service__client=client).exclude(
            status__in=[Payment.Status.PAID, Payment.Status.CANCELLED, Payment.Status.REFUNDED]
        ).select_related("renewal__service").order_by("-requested_at")
    return render(
        request,
        "client_portal/notifications.html",
        _base_context(
            request,
            page_title="Notificações",
            active_nav="notifications",
            unread_messages=unread_messages,
            upcoming_renewals=upcoming_renewals,
            pending_payments=pending_payments,
        ),
    )
