from rest_framework.response import Response
from django.shortcuts import get_object_or_404, render,redirect
from django.views import View
from codesofy.custom_config import get_local_date, set_user_profile,convert_to_decimal,get_privilleges,get_standard_text_input,get_unique_text,is_authorized,set_menu_items,get_global_master_details
from codesofy.master_details import ProductType
from constants.general_const import ActiveStatus
from core.serializers import AppSettingsLanguageSerializer, DeviceMoistureThresholdSerializer, LanguageSerializer
from core.utils import get_api_message, get_or_create_app_settings
from utils.product_utils import dict_diff, product_to_dict
from .models import AppSettings, DeviceMoistureThreshold, Language
from rest_framework import serializers
from django.contrib import messages
from django.db import transaction, IntegrityError
from django.views.decorators.cache import cache_control
from django.utils.decorators import method_decorator
from django.core.paginator import Paginator
from django.http import Http404, JsonResponse
from django.conf import settings
from django.contrib import messages
from django.db import transaction, IntegrityError, models
from rest_framework import viewsets, permissions, status
from django.db.models.deletion import RestrictedError
from django.db.models import ProtectedError
from rest_framework import viewsets, permissions
from rest_framework.views import APIView
from django.db.models import Q

menu_item = "language_registry"

def set_sub_menu_item(sub_menu_item,context):
    context = set_menu_items(menu_item,sub_menu_item,context)
    return context

def get_local_master_details():
    context = get_global_master_details()
    return context

def _get_customer(request):
    cust = getattr(request.user, "customer", None)
    if not cust:
        # Should not happen because HasCustomerProfile is applied, but keep safe
        raise Http404("Not found.")
    return cust
class HasCustomerProfile(permissions.BasePermission):
    """Allow only authenticated users that have a related .customer object."""
    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated and getattr(request.user, "customer", None))

class IsCustomerOwner(permissions.BasePermission):
    """
    Object-level permission helper for APIViews.
    Use it by calling `self.check_object_permissions(request, obj)` in your view after loading `obj`.
    Expects obj.customer_device.customer_id to match the current user's customer.id.
    """
    def has_object_permission(self, request, view, obj):
        cust = getattr(request.user, "customer", None)
        if not cust:
            return False
        device = getattr(obj, "customer_device", None)
        if not device:
            return False
        return getattr(device, "customer_id", None) == cust.id

def app_version(request):
    return {
        "APP_VERSION": getattr(settings, "APP_VERSION", "1.0.0")
    }

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class AddLanguageView(View):
    this_feature = "add_language"
    sub_menu_item = "language_list"

    def get(self, request):
        return redirect('language-list')

    def post(self, request):
        user_profile = set_user_profile(request, {})
        if user_profile is None:
            return redirect('login')

        if not is_authorized(user_profile, self.this_feature):
            return redirect('unauthorized-access')

        language_code = (request.POST.get('language_code') or '').strip()
        language_name = (request.POST.get('language_name') or '').strip()
        eng_name = (request.POST.get('eng_name') or '').strip()
        language_id = (request.POST.get('language_id') or '').strip()

        if not language_code or not language_name or not eng_name or not language_id:
            messages.error(request, "Language code, name, English name and ID are required.")
            return redirect('language-list')

        try:
            with transaction.atomic():
                if Language.objects.filter(code__iexact=language_code).exists():
                    messages.error(request, "Language code already exists.")
                    return redirect('language-list')

                if Language.objects.filter(eng_name__iexact=eng_name).exists():
                    messages.error(request, "English name already exists.")
                    return redirect('language-list')

                if Language.objects.filter(language_id=language_id).exists():
                    messages.error(request, "Language ID already exists.")
                    return redirect('language-list')

                Language.objects.create(
                    code=language_code,
                    name=language_name,
                    eng_name=eng_name,
                    language_id=language_id,
                    created_by=user_profile,
                    updated_by=user_profile
                )

                messages.success(request, "New language was created successfully.")
                return redirect('language-list')

        except Exception as exp:
            messages.error(request, f"Unexpected Error: {exp}")
            messages.error(request, "Error occurred in atomic operation. Contact the administrator.")
            return redirect('language-list')


@method_decorator(cache_control(no_cache=True, must_revalidate=True,no_store=True), name='dispatch')
class ViewLanguageView(View):

    this_feature = "view_language"
    sub_menu_item = "language_list"
    
    def get(self, request,id):

        context = get_local_master_details()

        user_profile = set_user_profile(request,context)

        if user_profile==None:
            return redirect('login')

        get_privilleges(user_profile,context)

        if not is_authorized(user_profile,self.this_feature):
            return redirect('unauthorized-access')
        
        set_sub_menu_item(self.sub_menu_item,context)

        try:
            with transaction.atomic():
                if Language.objects.filter(pk=id).exists():
                    language = Language.objects.get(pk=id)
                    context["language"] = language
                    return render(request, 'language.html', context)
                else:
                    return redirect ('language')
        except Exception or IntegrityError as exp:
            print (exp)
            messages.error(
                request, "Error Occured in atomic operation. Contact the System Administrator")
            return redirect ('language')
        
     
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class LanguageListView(View):

    this_feature = "language_list"
    sub_menu_item = "language_list"

    def get(self, request):
        context = get_local_master_details()

        user_profile = set_user_profile(request, context)
        if user_profile is None:
            return redirect('login')

        get_privilleges(user_profile, context)

        if not is_authorized(user_profile, self.this_feature):
            return redirect('unauthorized-access')

        set_sub_menu_item(self.sub_menu_item, context)

        per_page_count = request.GET.get('per_page_count', '10')
        page_number = request.GET.get('page_number', '1')

        context["default_per_page_count"] = str(per_page_count)

        language_list = Language.objects.all().order_by('language_id')
        context["has_entries"] = language_list.exists()

        paginator = Paginator(language_list, int(per_page_count))
        page = paginator.get_page(page_number)
        page_list = list(paginator.get_elided_page_range(page.number))

        context["page"] = page
        context["page_list"] = page_list

        return render(request, 'language.html', context)
    
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class UpdateLanguageView(View):
    this_feature  = "update_language"
    sub_menu_item = "language_list"

    def get(self, request, id):
        context = {}
        user_profile = set_user_profile(request, context)
        if user_profile is None:
            return redirect('login')

        get_privilleges(user_profile, context)
        if not is_authorized(user_profile, self.this_feature):
            return redirect('unauthorized-access')

        set_sub_menu_item(self.sub_menu_item, context)

        lang = get_object_or_404(Language, pk=id)
        context["language"] = lang
        context["old_input_field_values"] = {}
        return render(request, "update-language.html", context)

    def post(self, request):
        context = {}

        user_profile = set_user_profile(request, context)
        if user_profile is None:
            return redirect('login')

        get_privilleges(user_profile, context)
        if not is_authorized(user_profile, self.this_feature):
            return redirect('unauthorized-access')

        set_sub_menu_item(self.sub_menu_item, context)

        try:
            id_ = int(request.POST.get("id") or 0)
        except ValueError:
            messages.error(request, "Invalid language id.")
            return redirect("language-list")

        code = (request.POST.get("language_code") or "").strip()
        name = (request.POST.get("language_name") or "").strip()
        eng_name = (request.POST.get("eng_name") or "").strip()
        language_id = (request.POST.get("language_id") or "").strip()

        if not id_ or not code or not name or not eng_name or not language_id:
            messages.error(request, "Language id, code, name, English name and language id are required.")
            return redirect("language-list")

        lang = get_object_or_404(Language, pk=id_)

        if Language.objects.exclude(pk=id_).filter(code__iexact=code).exists():
            messages.error(request, "Language code already exists.")
            return redirect("language-list")

        if Language.objects.exclude(pk=id_).filter(eng_name__iexact=eng_name).exists():
            messages.error(request, "English name already exists.")
            return redirect("language-list")

        if Language.objects.exclude(pk=id_).filter(language_id=language_id).exists():
            messages.error(request, "Language ID already exists.")
            return redirect("language-list")

        try:
            with transaction.atomic():
                changed = False

                if lang.code != code:
                    lang.code = code
                    changed = True

                if lang.name != name:
                    lang.name = name
                    changed = True

                if lang.eng_name != eng_name:
                    lang.eng_name = eng_name
                    changed = True

                if lang.language_id != language_id:
                    lang.language_id = language_id
                    changed = True

                if not changed:
                    messages.info(request, "No changes detected.")
                    return redirect("language-list")

                lang.updated_by = user_profile
                lang.save(update_fields=["code", "name", "eng_name", "language_id", "updated_by", "updated_At"])

            messages.success(request, "Language updated successfully.")
            return redirect("language-list")

        except Exception as e:
            messages.error(request, f"Update failed: {e}")
            return redirect("language-list")

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeleteLanguageView(View):
    this_feature = "delete_language"

    # Handles: POST /delete-language (id is taken from form)
    def post(self, request):
        context = {}
        user_profile = set_user_profile(request, context)
        if user_profile is None:
            return redirect('login')

        get_privilleges(user_profile, context)
        if not is_authorized(user_profile, self.this_feature):
            return redirect('unauthorized-access')

        try:
            id_ = int(request.POST.get("id") or 0)
        except ValueError:
            messages.error(request, "Invalid language id.")
            return redirect("language-list")

        if not id_:
            messages.error(request, "Missing language id.")
            return redirect("language-list")

        lang = get_object_or_404(Language, pk=id_)

        try:
            with transaction.atomic():
                lang.delete()
            messages.success(request, "Language deleted.")
            return redirect("language-list")

        except ProtectedError:
            messages.error(request, "Deletion blocked: related data exists.")
            return redirect("language-list")
        except IntegrityError:
            messages.error(request, "Deletion failed due to related constraints.")
            return redirect("language-list")
        except Exception as e:
            messages.error(request, f"Unexpected error during delete: {e}")
            return redirect("language-list")

@method_decorator(cache_control(no_cache=True, must_revalidate=True,no_store=True), name='dispatch')
class LanguageList(APIView):
    permission_classes = [permissions.AllowAny]
    authentication_classes = []

    def get(self, request):
        languages = Language.objects.all().order_by("name")
        data = LanguageSerializer(languages, many=True).data
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("LANGUAGE_LIST", request),
                "code": "LANGUAGE_LIST",
                "data": data,
            },
            status=200,
        )


@method_decorator(cache_control(no_cache=True, must_revalidate=True,no_store=True), name='dispatch')
class LanguageDetail(APIView):
    permission_classes = [permissions.AllowAny]
    authentication_classes = []

    def get(self, request, id: int):
        lang = Language.objects.filter(pk=id).first()
        if not lang:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("LANGUAGE_NOT_FOUND",request),
                    "code": "LANGUAGE_NOT_FOUND",
                    "errors": {"id": [f"No language with id {id}."]},
                },
                status=404,
            )
        data = LanguageSerializer(lang).data
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("LANGUAGE_FETCHED",request),
                "code": "LANGUAGE_FETCHED",
                "data": data,
            },
            status=200,
        )
@method_decorator(cache_control(no_cache=True, must_revalidate=True,no_store=True), name='dispatch')
class TestView(View):
    def get(self, request):
        return JsonResponse(
            {
                "success": True,
                "message": "OK",
                "code": "TEST_OK",
                "data": {"message": "Test view is working!"},
            },
            status=200,
        )
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeviceMoistureThresholdListCreateAPI(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        try:
            cust = _get_customer(request)
            qs = (DeviceMoistureThreshold.objects
                  .select_related('customer_device', 'customer_device__product')
                  .filter(customer_device__customer_id=cust.id)
                  .order_by('customer_device_id'))
            ser = DeviceMoistureThresholdSerializer(qs, many=True)
            return JsonResponse(
                {
                    "success": True,
                    "message": get_api_message("THRESHOLD_LIST",request),
                    "code": "THRESHOLD_LIST",
                    "data": ser.data,
                },
                status=200,
            )
        except Exception as e:
            return JsonResponse(
                {"success": False, "message": get_api_message("THRESHOLD_LIST_FAILED",request), "code": "THRESHOLD_LIST_FAILED",
                 "errors": {"exception": [str(e)]}},
                status=500,
            )

    def post(self, request):
        try:
            ser = DeviceMoistureThresholdSerializer(data=request.data, context={'request': request})
            ser.is_valid(raise_exception=True)
            instance = ser.save()
            out = DeviceMoistureThresholdSerializer(instance).data
            return JsonResponse(
                {
                    "success": True,
                    "message": get_api_message("THRESHOLD_CREATED",request),
                    "code": "THRESHOLD_CREATED",
                    "data": out,
                },
                status=status.HTTP_201_CREATED,
            )
        except serializers.ValidationError as e:
            detail = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
            return JsonResponse(
                {"success": False, "message": get_api_message("VALIDATION_ERROR",request), "code": "VALIDATION_ERROR", "errors": detail},
                status=400,
            )
        except Exception as e:
            return JsonResponse(
                {"success": False, "message": get_api_message("THRESHOLD_CREATE_FAILED",request), "code": "THRESHOLD_CREATE_FAILED",
                 "errors": {"exception": [str(e)]}},
                status=500,
            )
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeviceMoistureThresholdDetailAPI(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile, IsCustomerOwner]

    def get_object(self, request, pk: int) -> DeviceMoistureThreshold:
        cust = _get_customer(request)
        obj = (
            DeviceMoistureThreshold.objects
            .select_related('customer_device', 'customer_device__product')
            .filter(pk=pk, customer_device__customer_id=cust.id)
            .first()
        )
        if not obj:
            raise Http404
        self.check_object_permissions(request, obj)
        return obj

    def get(self, request, pk: int):
        obj = self.get_object(request, pk)
        ser = DeviceMoistureThresholdSerializer(obj)
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("THRESHOLD_FETCHED", request),
                "code": "THRESHOLD_FETCHED",
                "data": ser.data,
            },
            status=200,
        )

    def patch(self, request, pk: int):
        try:
            obj = self.get_object(request, pk)
            ser = DeviceMoistureThresholdSerializer(
                obj, data=request.data, partial=True, context={'request': request}
            )
            ser.is_valid(raise_exception=True)
            ser.save()
            return JsonResponse(
                {
                    "success": True,
                    "message": get_api_message("THRESHOLD_UPDATED", request),
                    "code": "THRESHOLD_UPDATED",
                    "data": DeviceMoistureThresholdSerializer(obj).data,
                },
                status=200,
            )
        except serializers.ValidationError as e:
            detail = e.detail if isinstance(e.detail, dict) else {"non_field_errors": e.detail}
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("VALIDATION_ERROR", request),
                    "code": "VALIDATION_ERROR",
                    "errors": detail,
                },
                status=400,
            )

    def put(self, request, pk: int):
        return self.patch(request, pk)

    def delete(self, request, pk: int):
        obj = self.get_object(request, pk)
        obj.delete()
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("THRESHOLD_DELETED", request),
                "code": "THRESHOLD_DELETED",
                "data": {"deleted": True},
            },
            status=200,
        )

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeviceMoistureThresholdByProductTypeAPI(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        raw = (request.query_params.get("type") or "").strip()
        if not raw:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("MISSING_PRODUCT_TYPE", request),
                    "code": "MISSING_PRODUCT_TYPE",
                    "errors": {"type": ["e.g., GRAIN"]},
                },
                status=400,
            )

        cust = _get_customer(request)
        if not cust:
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("CUSTOMER_NOT_FOUND", request),
                    "code": "CUSTOMER_NOT_FOUND",
                    "errors": {"detail": ["Customer not linked to this user."]},
                },
                status=403,
            )

        enum_value = self._resolve_enum_value(raw)
        if enum_value is None:
            allowed = [str(m.value) for m in ProductType]
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("INVALID_PRODUCT_TYPE", request),
                    "code": "INVALID_PRODUCT_TYPE",
                    "errors": {"type": [f"Allowed: {allowed}"]},
                },
                status=400,
            )

        qs = (
            DeviceMoistureThreshold.objects
            .select_related('customer_device', 'customer_device__product')
            .filter(
                customer_device__customer_id=cust.id,
                customer_device__product__type=enum_value
            )
            .order_by('customer_device_id')
        )

        ser = DeviceMoistureThresholdSerializer(qs, many=True)
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("THRESHOLD_LIST_BY_PRODUCT_TYPE", request),
                "code": "THRESHOLD_LIST_BY_PRODUCT_TYPE",
                "data": ser.data,
            },
            status=200,
        )

    @staticmethod
    def _resolve_enum_value(raw: str):
        try:
            return ProductType[raw.upper()].value
        except Exception:
            for m in ProductType:
                if str(m.value).upper() == raw.upper():
                    return m.value
        return None


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeviceMoistureThresholdGroupedByProductTypeAPI(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        cust = _get_customer(request)
        qs = (
            DeviceMoistureThreshold.objects
            .select_related('customer_device', 'customer_device__product')
            .filter(customer_device__customer_id=cust.id)
            .order_by('customer_device_id')
        )

        data = DeviceMoistureThresholdSerializer(qs, many=True).data
        out = {}
        for row in data:
            out.setdefault(row.get("product_type"), []).append(row)

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("THRESHOLD_GROUPED_BY_PRODUCT_TYPE", request),
                "code": "THRESHOLD_GROUPED_BY_PRODUCT_TYPE",
                "data": out,
            },
            status=200,
        )

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class SupportCenterJsonHtmlView(APIView):
    permission_classes = []

    def get(self, request):
        html = """<div>...</div>"""

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("SUPPORT_CENTER", request),
                "code": "SUPPORT_CENTER",
                "data": {"support_center_html": html},
            },
            status=200,
        )

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class PrivacyPolicyView(APIView):
    permission_classes = []

    def get(self, request):
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("PRIVACY_POLICY", request),
                "code": "PRIVACY_POLICY",
                "data": {
                    "version": "V1",
                    "privacy_policy_url": "https://www.wile.fi/en/wile-connect-app-privacy-policy/",
                    "privacy_policy_html": "<html>...</html>",
                },
            },
            status=200,
        )

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class GetDefaultFieldAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        customer = request.user.customer
        settings = get_or_create_app_settings(customer)
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEFAULT_FIELD", request),
                "code": "DEFAULT_FIELD",
                "data": settings.default_field or None,
            },
            status=200,
        )


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class GetDefaultBuyerAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def get(self, request):
        customer = request.user.customer
        settings = get_or_create_app_settings(customer)
        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("DEFAULT_BUYER", request),
                "code": "DEFAULT_BUYER",
                "data": settings.default_buyer or None,
            },
            status=200,
        )

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name="dispatch")
class CustomerLanguageView(APIView):
    """
    GET   -> get current customer's language from AppSettings
    PUT   -> update language (requires language_id)
    PATCH -> same as PUT
    """
    permission_classes = [permissions.IsAuthenticated, HasCustomerProfile]

    def _get_app_settings(self, customer):
        app_settings, _ = AppSettings.objects.get_or_create(customer_user=customer)
        return app_settings

    def get(self, request):
        customer = request.user.customer
        app_settings = self._get_app_settings(customer)

        serializer = AppSettingsLanguageSerializer(app_settings)

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("CUSTOMER_LANGUAGE_FETCHED", request),
                "code": "CUSTOMER_LANGUAGE_FETCHED",
                "data": serializer.data,   # -> { "language": { id, code, name } }
            },
            status=status.HTTP_200_OK,
        )

    def put(self, request):
        customer = request.user.customer
        app_settings = self._get_app_settings(customer)

        serializer = AppSettingsLanguageSerializer(
            app_settings, data=request.data, partial=False
        )

        if not serializer.is_valid():
            
            return JsonResponse(
                {
                    "success": False,
                    "message": get_api_message("VALIDATION_ERROR", request),
                    "code": "VALIDATION_ERROR",
                    "errors": serializer.errors,
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        serializer.save()

        return JsonResponse(
            {
                "success": True,
                "message": get_api_message("CUSTOMER_LANGUAGE_UPDATED", request),
                "code": "CUSTOMER_LANGUAGE_UPDATED",
                "data": serializer.data,   # -> { "language": { id, code, name } }
            },
            status=status.HTTP_200_OK,
        )

    def patch(self, request):
        # behave same as PUT
        return self.put(request)