import json
import logging

from django.conf import settings
from django.contrib.auth.decorators import login_not_required
from django.core.mail import send_mail
from django.db import transaction
from django.http import JsonResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt

from servala.api.permissions import OSBBasicAuthPermission
from servala.core.exoscale import get_exoscale_origin
from servala.core.models import (
    BillingEntity,
    Organization,
    OrganizationInvitation,
    OrganizationRole,
    User,
)
from servala.core.models.service import Service, ServiceInstance, ServiceOffering
from servala.core.odoo import create_helpdesk_ticket

logger = logging.getLogger(__name__)


@method_decorator(csrf_exempt, name="dispatch")
@method_decorator(login_not_required, name="dispatch")
class OSBServiceInstanceView(OSBBasicAuthPermission, View):
    """
    OSB API endpoint for service instance management via Exoscale.
    """

    def _error(self, error):
        return JsonResponse({"error": error}, status=400)

    def _get_user(self, data):
        email = data.get("email", "").strip().lower()
        if not email:
            raise ValueError("Email address is required but missing or empty")

        full_name = data.get("full_name") or ""
        name_parts = full_name.split(" ", 1)
        first_name = name_parts[0] if name_parts else ""
        last_name = name_parts[1] if len(name_parts) > 1 else ""
        user, _ = User.objects.get_or_create(
            email=email,
            defaults={"first_name": first_name, "last_name": last_name},
        )
        return user

    def put(self, request, instance_id):
        """
        This implements the Exoscale onboarding flow.
        https://docs.servala.com/exoscale-osb.html#_onboarding
        https://community.exoscale.com/vendor/marketplace-managed-svc-prov/#provisioning
        """
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON in request body"}, status=400)

        context = data.get("context", {})
        parameters = data.get("parameters", {})

        organization_guid = context.get("organization_guid") or data.get(
            "organization_guid"
        )
        organization_name = context.get("organization_name")
        organization_display_name = context.get(
            "organization_display_name", organization_name
        )
        users = parameters.get("users", [])
        service_id = data.get("service_id")
        plan_id = data.get("plan_id")

        if not organization_guid:
            return self._error("organization_guid is required but missing.")
        if not organization_name:
            return self._error("organization_name is required but missing.")
        if not users:
            return self._error("users array is required but missing.")
        if len(users) != 1:
            return self._error("users array is expected to contain a single user.")
        if not service_id:
            return self._error("service_id is required but missing.")
        if not plan_id:
            return self._error("plan_id is required but missing.")

        try:
            user = self._get_user(users[0])
        except Exception as e:
            return self._error(f"Unable to create user: {e}")

        try:
            service = Service.objects.get(osb_service_id=service_id)
            service_offering = ServiceOffering.objects.get(
                osb_plan_id=plan_id, service=service
            )
        except Service.DoesNotExist:
            return self._error(f"Unknown service_id: {service_id}")
        except ServiceOffering.DoesNotExist:
            return self._error(
                f"Unknown plan_id: {plan_id} for service_id: {service_id}"
            )

        exoscale_origin = get_exoscale_origin()
        try:
            organization = Organization.objects.get(
                osb_guid=organization_guid, origin=exoscale_origin
            )
            if service in organization.limit_osb_services.all():
                return JsonResponse({"message": "Service already enabled"}, status=200)
        except Organization.DoesNotExist:
            try:
                with transaction.atomic():
                    if exoscale_origin.billing_entity:
                        billing_entity = exoscale_origin.billing_entity
                    else:
                        odoo_data = {
                            "company_name": organization_display_name,
                            "invoice_email": user.email,
                        }
                        billing_entity = BillingEntity.create_from_data(
                            name=f"{organization_display_name} (Exoscale)",
                            odoo_data=odoo_data,
                        )
                    organization = Organization(
                        name=organization_display_name,
                        billing_entity=billing_entity,
                        origin=exoscale_origin,
                        osb_guid=organization_guid,
                    )
                    organization = Organization.create_organization(organization)
                    invitation = OrganizationInvitation.objects.create(
                        organization=organization,
                        email=user.email.lower(),
                        role=OrganizationRole.OWNER,
                    )
                    invitation.send_invitation_email(request)
            except Exception:
                return JsonResponse({"error": "Internal server error"}, status=500)

        organization.limit_osb_services.add(service)
        self._send_service_welcome_email(
            request, organization, user, service, service_offering
        )
        return JsonResponse({"message": "Successfully enabled service"}, status=201)

    def _send_service_welcome_email(
        self, request, organization, user, service, service_offering
    ):
        service_path = f"{organization.urls.services}{service.slug}/offering/{service_offering.id}/"
        service_url = request.build_absolute_uri(service_path)

        subject = f"Get started with {service.name} - {organization.name}"
        message = f"""Hello {user.first_name or user.email},

Your organization "{organization.name}" is now ready on Servala Portal!

You can create your {service.name} service directly here:
{service_url}

Or browse all available services at: {request.build_absolute_uri(organization.urls.services)}

Need help? Contact our support team through the portal.

Best regards,
The Servala Team"""

        send_mail(
            subject=subject,
            message=message,
            from_email=settings.EMAIL_DEFAULT_FROM,
            recipient_list=[user.email],
            fail_silently=False,
        )

    def delete(self, request, instance_id):
        """
        This implements the Exoscale offboarding flow MVP.
        https://docs.servala.com/exoscale-osb.html#_offboarding
        """
        service_id = request.GET.get("service_id")
        plan_id = request.GET.get("plan_id")

        if not service_id:
            return self._error("service_id is required but missing.")
        if not plan_id:
            return self._error("plan_id is required but missing.")

        try:
            service = Service.objects.get(osb_service_id=service_id)
            service_offering = ServiceOffering.objects.get(
                osb_plan_id=plan_id, service=service
            )
        except Service.DoesNotExist:
            return self._error(f"Unknown service_id: {service_id}")
        except ServiceOffering.DoesNotExist:
            return self._error(
                f"Unknown plan_id: {plan_id} for service_id: {service_id}"
            )

        self._create_action_helpdesk_ticket(
            request=request,
            action="Offboard",
            instance_id=instance_id,
            service=service,
            service_offering=service_offering,
        )

        return JsonResponse({}, status=200)

    def patch(self, request, instance_id):
        """
        This implements the Exoscale suspension flow MVP.
        https://docs.servala.com/exoscale-osb.html#_suspension
        """
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON in request body"}, status=400)

        service_id = data.get("service_id")
        plan_id = data.get("plan_id")

        if not service_id:
            return self._error("service_id is required but missing.")
        if not plan_id:
            return self._error("plan_id is required but missing.")

        try:
            service = Service.objects.get(osb_service_id=service_id)
            # Special handling: when plan_id is "suspend", don't lookup service_offering
            service_offering = None
            if plan_id != "suspend":
                service_offering = ServiceOffering.objects.get(
                    osb_plan_id=plan_id, service=service
                )
        except Service.DoesNotExist:  # pragma: no cover
            return self._error(f"Unknown service_id: {service_id}")
        except ServiceOffering.DoesNotExist:  # pragma: no cover
            return self._error(
                f"Unknown plan_id: {plan_id} for service_id: {service_id}"
            )

        self._create_action_helpdesk_ticket(
            request=request,
            action="Suspend",
            instance_id=instance_id,
            service=service,
            service_offering=service_offering,
            users=data.get("parameters", {}).get("users"),
        )
        return JsonResponse({}, status=200)

    def _get_admin_url(self, model_name, pk):
        admin_path = reverse(f"admin:{model_name}", args=[pk])
        return self.request.build_absolute_uri(admin_path)

    def _create_action_helpdesk_ticket(
        self, request, action, instance_id, service, service_offering=None, users=None
    ):
        """
        Create an Odoo helpdesk ticket for offboarding or suspension actions.
        This is an MVP implementation that creates a ticket for manual handling.
        """
        try:
            service_instance = None
            organization = None
            try:
                # Look for instances with this name in the service offering's context
                filter_kwargs = {"name": instance_id}
                if service_offering:
                    filter_kwargs["context__service_offering"] = service_offering

                service_instance = (
                    ServiceInstance.objects.filter(**filter_kwargs)
                    .select_related("organization")
                    .first()
                )

                if service_instance:
                    organization = service_instance.organization
            except Exception:  # pragma: no cover
                pass

            description_parts = [f"Action: {action}", f"Service: {service.name}"]
            if organization:
                org_url = self._get_admin_url(
                    "core_organization_change", organization.pk
                )
                description_parts.append(
                    f"Organization: {organization.name} - {org_url}"
                )

            if service_instance:
                instance_url = self._get_admin_url(
                    "core_serviceinstance_change", service_instance.pk
                )
                description_parts.append(
                    f"Instance: {service_instance.display_name} ({service_instance.name}) - {instance_url}"
                )
            else:
                description_parts.append(f"Instance: {instance_id}")

            if service_offering:
                offering_url = self._get_admin_url(
                    "core_serviceoffering_change", service_offering.pk
                )
                description_parts.append(f"Service Offering: {offering_url}")

            if users:
                description_parts.append("<br/>Users:")
                for user_data in users:
                    email = user_data.get("email", "N/A")
                    full_name = user_data.get("full_name", "N/A")
                    role = user_data.get("role", "N/A")

                    user_link = email
                    if email and email != "N/A":
                        try:
                            user = User.objects.get(email=email.strip().lower())
                            user_link = self._get_admin_url("core_user_change", user.pk)
                        except User.DoesNotExist:
                            pass

                    description_parts.append(f"  - {full_name} ({user_link}) - {role}")

            description = "<br/>".join(description_parts)

            create_helpdesk_ticket(
                title=f"Exoscale OSB {action} - {service.name} - {instance_id}",
                description=description,
            )
            logger.info(
                f"Created {action} helpdesk ticket for instance {instance_id}, service {service.name}"
            )

        except Exception as e:
            logger.error(
                f"Error creating Exoscale {action} helpdesk ticket for instance {instance_id}: {e}"
            )
