
import re
from django.utils import timezone
import math
from django.contrib import messages
from django.db import IntegrityError,transaction
from django.shortcuts import get_object_or_404, redirect, render
from django.views import View

from codesofy.custom_config import get_global_master_details, get_privilleges, is_authorized, set_menu_items, set_user_profile
from codesofy.master_details import PerPageSelector, ProductType, UserAccountStatus
from constants.general_const import ActiveStatus
from cropmanagement.models import Crop, CropTranslation
from usermanagement.models import UserProfile
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db.models import RestrictedError
from django.views.decorators.cache import cache_control
from django.utils.decorators import method_decorator
from core.models import Language

# Create your views here.

from django.db.models import Q

menu_item = "crop_registry"


_SPACE_RE = re.compile(r"\s+")

def _norm_text(val):
    """Return a trimmed single-spaced string; handle None/non-strings safely."""
    if not isinstance(val, str):
        return ""
    return _SPACE_RE.sub(" ", val).strip()

def get_active_counsellors():
    counsellors = UserProfile.objects.filter(user__is_active=True)
    return counsellors

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_local_master_details_for_user_details():
    
    context = get_local_master_details()
    user_account_statuses = UserAccountStatus.to_list_for_reports()
    context["user_account_statuses"] = user_account_statuses

    return context



def get_local_master_details_for_add_update_crop():
    
    context = get_local_master_details()
    type_list = ProductType.to_list()
    crop_status = ActiveStatus.to_list()
    context["crop_status_list"] = crop_status
    context['type_list'] = type_list
    
    return context

def get_local_master_details_for_crop_details():
    
    context = get_local_master_details()
    type_list = ProductType.to_list_for_reports()
    crop_status = ActiveStatus.to_list_for_reports()
    context["crop_status_list"] = crop_status
    context['type_list'] = type_list
    
    return context

def get_local_master_details_for_add_update_crop_translation():
    context = get_local_master_details()
    context["crop_list"] = Crop.objects.order_by("name")
    context["language_list"] = Language.objects.order_by("name")
    return context


def get_local_master_details_for_crop_translation_details():
    context = get_local_master_details()
    context["crop_list"] = Crop.objects.order_by("name")
    context["language_list"] = Language.objects.order_by("name")
    return context

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

    def get(self, request):
        context = get_local_master_details_for_add_update_crop()
        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)
        return render(request, 'add-crop.html', context)

    def post(self, request):
        context = get_local_master_details_for_add_update_crop()
        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)

        # ---- inputs ----
        name = (request.POST.get('name') or '').strip()
        crop_type = (request.POST.get('type') or '').strip()
        crop_id = (request.POST.get('crop_id') or '').strip()

        if not name or not crop_type or not crop_id:
            messages.error(request, "Crop name and type and crop_id are required.")
            # keep user input on the form
            context['form_values'] = {'name': name, 'type': crop_type}
            return render(request, 'add-crop.html', context)

        # normalize whitespace
        name = " ".join(name.split())

        # ---- pre-check (case-insensitive) ----
        if Crop.objects.filter(name__iexact=name).exists():
            messages.error(request, f'Crop "{name}" already exists.')
            context['form_values'] = {'name': name, 'type': crop_type}
            return render(request, 'add-crop.html', context)

        # ---- create with race safety ----
        try:
            with transaction.atomic():
                crop = Crop(
                    crop_id=crop_id,
                    name=name,
                    type=crop_type,
                    is_active=ActiveStatus.ACTIVE.value,
                    created_user=user_profile,
                    updated_user=user_profile,
                )
                crop.save()
        except IntegrityError as e:
            print("ADD CROP IntegrityError:", repr(e))  # check your runserver console
            messages.error(request, f"DB error: {str(e)}")
            context['form_values'] = {'name': name, 'type': crop_type, 'crop_id': crop_id}
            return render(request, 'add-crop.html', context)

        # success: use PRG pattern → go to detail page of the new crop
        messages.success(request, f'Crop "{crop.name}" created successfully.')
        return redirect('view-crop', id=crop.id)   # <-- make sure this URL name exists and expects `id`
    
    
@method_decorator(cache_control(no_cache=True, must_revalidate=True,no_store=True), name='dispatch')
class ViewCropView(View):
 
    this_feature = "view_crop"
    sub_menu_item = "crop_details"

    def get(self, request, id):

        context = get_local_master_details_for_crop_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 Crop.objects.filter(pk=id).exists():
                    crop = Crop.objects.get(pk=id)                  
                    context["crop"]= crop
                    return render(request, 'view-crop.html', context)
                else:
                    return redirect('crop-details')
        except Exception or IntegrityError as exp:
            messages.error(
            request, "Error Occured in atomic operation. Contact the System Administrator")
            return redirect('crop-details')
        
@method_decorator(cache_control(no_cache=True, must_revalidate=True,no_store=True), name='dispatch')
class CropDetailsView(View):

    this_feature = "crop_details"
    sub_menu_item = "crop_details"

    def get(self, request):

        context = get_local_master_details_for_crop_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)


        raw_crop_name = request.GET.get("crop_name", "")          # could be None
        crop_name = _norm_text(raw_crop_name)                     # always a string
        context["default_crop_name"] = crop_name
        crop_type = request.GET.get('crop_type')

        is_active = request.GET.get('is_active')
        


        per_page_count = request.GET.get('per_page_count')
        page_number = request.GET.get('page_number')
        start_index = request.GET.get('start_index')

        if not page_number:
            page_number = 1

        if not per_page_count:
            per_page_count = PerPageSelector.get_default().value

        if start_index:
            page_number = math.ceil(int(start_index)/int(per_page_count))

        #filter logic applies here

        filter_kwargs = {}
        query = Q()

        if crop_type:
            if crop_type.lower()!="all":
                context["default_crop_type"] = ProductType(crop_type).value
                filter_kwargs['type']= ProductType(crop_type).value
        

        if is_active:
            if is_active.lower()!="all":
                context["default_is_active"] = ActiveStatus(is_active).value
                filter_kwargs['is_active']= ActiveStatus(is_active).value
                
        if crop_name:
                # Option A: partial match (most user-friendly)
            context['default_crop_name'] = crop_name  
            filter_kwargs["name__icontains"]=crop_name
        

       
        crop_list = Crop.objects.filter(**filter_kwargs).order_by('-id')

        if len(crop_list)>0:
            context["has_entries"] = True
        else:
            context["has_entries"] = False

        paginator = Paginator(crop_list,per_page_count)
        page = Paginator.get_page(paginator,page_number)
        page_list = list(paginator.get_elided_page_range(page_number, on_each_side=1))
        
        context["page"] = page
        context["page_list"] = page_list

        return render(request, 'crop-details.html', context)
    
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class UpdateCropView(View):

    this_feature = "update_crop"
    sub_menu_item = "crop_details"

    def get(self, request, id):
        context = get_local_master_details_for_add_update_crop()
        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)

        crop = get_object_or_404(Crop, pk=id)
        context["crop"] = crop
        # optional: context["old_input_field_values"] not needed on GET
        return render(request, 'update-crop.html', context)

    def post(self, request, id):
        context = get_local_master_details_for_crop_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)
        context["old_input_field_values"] = request.POST

        crop = Crop.objects.filter(pk=id).first()
        if not crop:
            messages.error(request, "Crop not found.")
            return render(request, 'update-crop.html', context)

        # Read & normalize inputs
        name = (request.POST.get("name") or "").strip()
        ctype = (request.POST.get("type") or "").strip()
        crop_id = (request.POST.get("crop_id") or "").strip()
        is_active = request.POST.get("is_active") 

        if not name or not  crop_id or not ctype :
            messages.error(request, "Name and type and crop_id is required.")
            return render(request, 'update-crop.html', context)

        try:
            with transaction.atomic():
                crop.crop_id = crop_id
                crop.name = name
                crop.type = ctype
                crop.is_active = is_active
                crop.updated_user = user_profile
                crop.updated_at = timezone.now()
                crop.save()

            messages.success(request, "Crop updated successfully.")
            return redirect('crop-details')
        except IntegrityError:
            messages.error(request, "A database error occurred. Please try again.")
            return render(request, 'update-crop.html', context)
        except Exception:
            messages.error(request, "Unexpected error. Contact the system administrator.")
            return render(request, 'update-crop.html', context)

@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeleteCropView(View):
    this_feature = "delete_crop"
    sub_menu_item = "crop_details"

    def post(self, request):
        context = get_local_master_details_for_crop_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)

        crop_id = request.POST.get('crop_id')
        if not crop_id:
            messages.error(request, "No crop id provided.")
            return redirect('crop-details')

        try:
            with transaction.atomic():
                # Lock the row to avoid race conditions during deletion
                crop = Crop.objects.select_for_update().get(pk=crop_id)
                crop.delete()
                messages.success(request, f'Crop "{crop.name}" deleted.')
                return redirect('crop-details')

        except Crop.DoesNotExist:
            messages.error(request, "Crop not found.")
            return redirect('crop-details')
        except RestrictedError:
            messages.error(request, "Deletion restricted: this crop is referenced by other records.")
            return redirect('crop-details')
        except Exception as e:
            messages.error(request, f"An error occurred: {e}")
            return redirect('crop-details')      


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

    def get(self, request):
        context = get_local_master_details_for_add_update_crop_translation()
        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)
        return render(request, 'add-crop-translation.html', context)

    def post(self, request):
        context = get_local_master_details_for_add_update_crop_translation()
        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)
        context["old_input_field_values"] = request.POST

        crop_id = (request.POST.get("crop_id") or "").strip()
        language_id = (request.POST.get("language_id") or "").strip()
        name = (request.POST.get("name") or "").strip()
        name = " ".join(name.split())

        if not crop_id or not language_id or not name:
            messages.error(request, "Crop, language, and translation name are required.")
            return render(request, 'add-crop-translation.html', context)

        crop = Crop.objects.filter(pk=crop_id).first()
        if not crop:
            messages.error(request, "Invalid crop selected.")
            return render(request, 'add-crop-translation.html', context)

        language = Language.objects.filter(pk=language_id).first()
        if not language:
            messages.error(request, "Invalid language selected.")
            return render(request, 'add-crop-translation.html', context)

        # Only one translation per crop + language
        if CropTranslation.objects.filter(crop=crop, language=language).exists():
            messages.error(request, f'A translation already exists for "{crop.name}" in "{language.name}".')
            return render(request, 'add-crop-translation.html', context)

        try:
            with transaction.atomic():
                obj = CropTranslation(
                    crop=crop,
                    language=language,
                    name=name,
                    created_user=user_profile,
                    updated_user=user_profile,
                )
                obj.save()

            messages.success(request, "Crop translation created successfully.")
            return redirect('view-crop-translation', id=obj.id)

        except IntegrityError as e:
            messages.error(request, f"DB error: {str(e)}")
            return render(request, 'add-crop-translation.html', context)
        except Exception:
            messages.error(request, "Unexpected error. Contact the system administrator.")
            return render(request, 'add-crop-translation.html', context)


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class ViewCropTranslationView(View):
    this_feature = "view_crop_translation"
    sub_menu_item = "crop_translation_details"

    def get(self, request, id):
        context = get_local_master_details_for_crop_translation_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)

        obj = CropTranslation.objects.select_related("crop", "language").filter(pk=id).first()
        if not obj:
            return redirect('crop-translation-details')

        context["crop_translation"] = obj
        return render(request, 'view-crop-translation.html', context)


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

    def get(self, request):
        context = get_local_master_details_for_crop_translation_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)

        # Filters
        raw_name = request.GET.get("name", "")
        name = _norm_text(raw_name)
        crop_id = (request.GET.get("crop_id") or "").strip()
        language_id = (request.GET.get("language_id") or "").strip()

        if name:
            context["default_name"] = name
        if crop_id:
            context["default_crop_id"] = crop_id
        if language_id:
            context["default_language_id"] = language_id

        per_page_count = request.GET.get('per_page_count') or PerPageSelector.get_default().value
        page_number = request.GET.get('page_number') or 1
        start_index = request.GET.get('start_index')
        if start_index:
            page_number = math.ceil(int(start_index) / int(per_page_count))

        filter_kwargs = {}
        if crop_id and crop_id.lower() != "all":
            filter_kwargs["crop_id"] = crop_id
        if language_id and language_id.lower() != "all":
            filter_kwargs["language_id"] = language_id
        if name:
            filter_kwargs["name__icontains"] = name

        qs = (
            CropTranslation.objects
            .select_related("crop", "language")
            .filter(**filter_kwargs)
            .order_by("-id")
        )

        context["has_entries"] = qs.exists()

        paginator = Paginator(qs, per_page_count)
        page = Paginator.get_page(paginator, page_number)
        page_list = list(paginator.get_elided_page_range(page_number, on_each_side=1))

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

        return render(request, 'crop-translation-details.html', context)


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class UpdateCropTranslationView(View):
    this_feature = "update_crop_translation"
    sub_menu_item = "crop_translation_details"

    def get(self, request, id):
        context = get_local_master_details_for_add_update_crop_translation()
        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)

        obj = CropTranslation.objects.select_related("crop", "language").filter(pk=id).first()
        if not obj:
            return redirect('crop-translation-details')

        context["crop_translation"] = obj
        return render(request, 'update-crop-translation.html', context)

    def post(self, request, id):
        context = get_local_master_details_for_add_update_crop_translation()
        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)
        context["old_input_field_values"] = request.POST

        obj = CropTranslation.objects.filter(pk=id).first()
        if not obj:
            messages.error(request, "Crop translation not found.")
            return redirect('crop-translation-details')

        crop_id = (request.POST.get("crop_id") or "").strip()
        language_id = (request.POST.get("language_id") or "").strip()
        name = (request.POST.get("name") or "").strip()
        name = " ".join(name.split())

        if not crop_id or not language_id or not name:
            messages.error(request, "Crop, language, and translation name are required.")
            context["crop_translation"] = obj
            return render(request, 'update-crop-translation.html', context)

        crop = Crop.objects.filter(pk=crop_id).first()
        language = Language.objects.filter(pk=language_id).first()
        if not crop or not language:
            messages.error(request, "Invalid crop or language selected.")
            context["crop_translation"] = obj
            return render(request, 'update-crop-translation.html', context)

        # Uniqueness checks
        # Only one translation per crop + language
        if CropTranslation.objects.filter(crop=crop, language=language).exclude(pk=obj.id).exists():
            messages.error(request, f'A translation already exists for "{crop.name}" in "{language.name}".')
            context["crop_translation"] = obj
            return render(request, 'update-crop-translation.html', context)

        try:
            with transaction.atomic():
                obj.crop = crop
                obj.language = language
                obj.name = name
                obj.updated_user = user_profile
                obj.updated_at = timezone.now()
                obj.save()

            messages.success(request, "Crop translation updated successfully.")
            return redirect('crop-translation-details')

        except IntegrityError:
            messages.error(request, "A database error occurred. Please try again.")
            context["crop_translation"] = obj
            return render(request, 'update-crop-translation.html', context)
        except Exception:
            messages.error(request, "Unexpected error. Contact the system administrator.")
            context["crop_translation"] = obj
            return render(request, 'update-crop-translation.html', context)


@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class DeleteCropTranslationView(View):
    this_feature = "delete_crop_translation"
    sub_menu_item = "crop_translation_details"

    def post(self, request):
        context = get_local_master_details_for_crop_translation_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)

        translation_id = request.POST.get("translation_id")
        if not translation_id:
            messages.error(request, "No translation id provided.")
            return redirect('crop-translation-details')

        try:
            with transaction.atomic():
                obj = CropTranslation.objects.select_for_update().get(pk=translation_id)
                obj.delete()
                messages.success(request, f'Translation "{obj.name}" deleted.')
                return redirect('crop-translation-details')

        except CropTranslation.DoesNotExist:
            messages.error(request, "Crop translation not found.")
            return redirect('crop-translation-details')
        except RestrictedError:
            messages.error(request, "Deletion restricted: this record is referenced by other records.")
            return redirect('crop-translation-details')
        except Exception as e:
            messages.error(request, f"An error occurred: {e}")
            return redirect('crop-translation-details')
    
@method_decorator(cache_control(no_cache=True, must_revalidate=True, no_store=True), name='dispatch')
class CropTranslationMatrixView(View):
    this_feature = "crop_translation_details_matrix"
    sub_menu_item = "crop_translation_details_matrix"

    def get(self, request):
        context = get_local_master_details_for_crop_translation_details()
        user_profile = set_user_profile(request, context)
        if user_profile is None:
            return redirect('login')  # or 'user-login' if that's your real url name

        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)

        languages = Language.objects.all().order_by("id")
        crops = Crop.objects.all().order_by("id")

        translations = (
            CropTranslation.objects
            .select_related("crop", "language")
            .values("crop_id", "language_id", "name")
        )

        tmap = {(t["crop_id"], t["language_id"]): t["name"] for t in translations}

        # keep existing context values (user_profile, menus, privileges, etc.)
        context.update({
            "languages": languages,
            "crops": crops,
            "tmap": tmap,
        })

        return render(request, "crop-translation-matrix.html", context)