from django.shortcuts import render, redirect
from django.urls import resolve, reverse
from django.contrib.auth import logout, login
from django.contrib.auth.models import User
from django.http import HttpResponse
from .navbar import NavBar
from django.contrib.auth.forms import AuthenticationForm as AuthForm
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash
from django.views import View
import django.forms as forms
from django.views.generic import TemplateView, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from .models import Storage, Stock, Component, Distributor, Manufacturer, Package, ComponentParameter, ComponentParameterType, DistributorNum
from .qr_parser import QrCodeValidator
from django.core.paginator import Paginator
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.db.models import ProtectedError
from .forms import *
from django.db.models import Q
from django.db.models.functions import Lower
import uuid

class QrSearchForm(forms.Form):
    my_qr_validator = QrCodeValidator()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        

    qr_search = forms.CharField(label='qr_search', validators=[my_qr_validator])

class BaseTemplateMixin(object):
    navbar_selected = ''
    base_title = ''

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        base_context = {
            'navbar': NavBar.get_navbar(self.navbar_selected, self.request.user),
            'title': NavBar.get_brand()+' / '+ self.base_title,
            'login_active': False,
        }
        context['base'] = base_context 
        return context

    def post(self, request, *args, **kwargs):
        data = request.POST
        if 'qr_search' not in data:
            super().post(request, *args, **kwargs)

        print('QR',data['qr_search'])
        f = QrSearchForm(data)
        if f.is_valid():
            return redirect(f.my_qr_validator.get_redirect_url(f.cleaned_data['qr_search']))

        return self.get(request)

class ChangePasswordView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
    template_name = 'parts/change-pw.html'
    navbar_selected = 'Main'
    base_title = 'Change Password'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = PasswordChangeForm(self.request.user)
        return context

    def post(self, request, *args, **kwargs):
        if 'submit-change-pw' not in request.POST:
            return super().post(request, *args, **kwargs)

        form = PasswordChangeForm(request.user, data=request.POST)

        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            return redirect('parts-main')
        else:
            pass

        context = self.get_context_data(**kwargs)
        if form.errors:
            context['form'] = form

        return self.render_to_response(context)
    

class MainView(BaseTemplateMixin, TemplateView):
    template_name = 'parts/main.html'
    navbar_selected = 'Main'
    base_title = 'Main'
    
def logout_view(request):
    logout(request)
    return redirect('parts-main')

def login_view(request):
    base_context = {
        'navbar': NavBar.get_navbar('Login', request.user),
        'title': NavBar.get_brand()+' / '+'Login',
        'login_active': True,
    }

    if request.user.is_authenticated:
        next_param = request.GET.get('next')
        if next_param is not None:
            return redirect(next_param)
        else:
            return redirect('parts-main')

    if request.method == 'POST':
        form = AuthForm(data=request.POST)
        if form.is_valid():
            valid_user = form.get_user()
            login(request, valid_user)
            next_param = request.GET.get('next')
            if next_param is not None:
                return redirect(next_param)
            else:
                return redirect('parts-main')
    else:
        form = AuthForm()


    context = {
        'base': base_context,
        'form': form,
    }

    return render(request, 'parts/login.html', context)

# Create your views here.

class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
    template_name = 'parts/components.html'
    base_title = 'Components'
    navbar_selected = 'Components'
    default_page_size = 25

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        comp_page_num = self.request.GET.get('comp_page', default=1)

        comp_paginator = Paginator(Component.objects.all(), self.default_page_size)

        context['components'] = comp_paginator.get_page(comp_page_num)
        return context
    
class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
    template_name = 'parts/packages.html'
    base_title = 'Packages'
    navbar_selected = 'Packages'
    default_page_size = 25

    def search_packages(self, search):
        qs = Package.objects.all()

        if not search:
            return qs
        
        search_fragments = search.strip().split()
        for search in search_fragments:
            if search.lower() == 'smd':
                s_filter = Q(name__icontains = search) | Q(smd = True)
            else:
                try:
                    pin_count = int(search)
                    s_filter = Q(name__icontains = search) | Q(pin_count=pin_count)
                except:
                    s_filter = Q(name__icontains = search)

            qs = qs.filter(s_filter)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        page_num = self.request.GET.get('page', default=1)
        search_string = self.request.GET.get('search', default=None)
        
        package_queryset = self.search_packages(search_string)

        paginator = Paginator(package_queryset, self.default_page_size)       


        context['search_string'] = search_string
        context['packages'] = paginator.get_page(page_num)
        context['new_pkg_form'] = PackageForm()

        return context
    
    def handle_add_new_package(self, request):

        form = PackageForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            form.save()

        context = self.get_context_data()

        if not form.is_valid():
            context['new_pkg_form'] = form

        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        if 'submit-pkg-add-new' in request.POST:
            return self.handle_add_new_package(request)

        return super().post(request, *args, **kwargs)
    

class DistributorView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
    template_name = 'parts/distributors.html'
    base_title = 'Distributors'
    navbar_selected = 'Distributors'
    default_page_size = 25

    def search_distributors(self, search):
        qs = Distributor.objects.all()

        if not search:
            return qs
        
        search_fragments = search.strip().split()

        for search in search_fragments:
            qs = qs.filter(Q(name__icontains = search) | Q(website__icontains = search))
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        page_num = self.request.GET.get('page', default=1)
        search_string = self.request.GET.get('search', default=None)
        
        queryset = self.search_distributors(search_string)

        paginator = Paginator(queryset, self.default_page_size)       


        context['search_string'] = search_string
        context['distributors'] = paginator.get_page(page_num)
        context['new_distri_form'] = DistributorForm()

        return context
    
    def handle_add_new_distributor(self, request):

        form = DistributorForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            form.save()

        context = self.get_context_data()

        if not form.is_valid():
            context['new_distri_form'] = form

        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        if 'submit-distri-add-new' in request.POST:
            return self.handle_add_new_distributor(request)

        return super().post(request, *args, **kwargs)

class StockView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
    template_name = 'parts/stocks.html'
    base_title = 'Stocks'
    navbar_selected = 'Stocks'
    default_pagination_size = 25

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        storage_page = self.request.GET.get('storage_page')
        if storage_page is None:
            storage_page = 1
        low_stock_page = self.request.GET.get('low_stock_page')
        if low_stock_page is None:
            low_stock_page = 1

        storage_paginator = Paginator(Storage.objects.filter(parent_storage=None), self.default_pagination_size)
        low_stock_paginator = Paginator(Stock.get_under_watermark(),
            self.default_pagination_size)

        context['low_stocks'] = low_stock_paginator.get_page(low_stock_page)
        context['storages'] = storage_paginator.get_page(storage_page)
        add_stor_form = AddSubStorageForm()
        add_stor_form.fields['responsible'].initial = self.request.user.username
        context['add_storage_form'] = add_stor_form
        return context

    def handle_add_storage(self, request, **kwargs):
        return_invalid_form = False

        f = AddSubStorageForm(data=request.POST)
        if f.is_valid():
            new_storage_name = f.cleaned_data['storage_name']
            try: 
                resp_user = User.objects.get(username=f.cleaned_data['responsible'])
            except Exception as _:
                resp_user = None
                f.add_error('responsible', 'Invalid Responsible User')
                return_invalid_form = True

            if resp_user is not None:
                try:
                    Storage.objects.create(name=new_storage_name, responsible=resp_user, parent_storage=None)
                except ValidationError as verr:
                    return_invalid_form = True
                    f.add_error('storage_name', ' .'.join(verr.messages))
        else:
            return_invalid_form = True

        context = self.get_context_data(**kwargs)
        if return_invalid_form:
            context['add_storage_form'] = f

        return self.render_to_response(context)
    
    def post(self, request, **kwargs):
        if 'submit-add-storage' in request.POST:
            return self.handle_add_storage(request, **kwargs)

        return super().post(request, **kwargs)
    
class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
    template_name = 'parts/stocks-detail.html'
    model = Storage
    pk_url_kwarg = 'uuid'
    base_title = ''
    navbar_selected = 'Stocks'
    default_pagination_size = 8

    def get_breadcrumbs(self):
        crumbs = self.object.get_path_components()
        # Reverse list and drop the last element of the reversed list
        crumbs = crumbs[::-1][:-1]
        return crumbs

    def search_stock_queryset(self, search):
        stocks_in_storage = Stock.objects.filter(storage=self.object).order_by(Lower('component__name'))
        
        if search is None or search == '':
            return stocks_in_storage

        if search.startswith('[comp_uuid]'):
            search = search.replace('[comp_uuid]', '')


        # Check if the searhc equals a UUID
        test_uuid = None
        try:
            test_uuid = uuid.UUID(search)
        except:
            pass

        if test_uuid is not None:
            stocks_in_storage = stocks_in_storage.filter(Q(component__id = test_uuid) | Q(id= test_uuid))
        else:
            stocks_in_storage = stocks_in_storage.filter(Q(component__name__icontains = search) |
                Q(component__package__name__icontains = search) |
                Q(component__manufacturer__name__icontains = search))
        
        return stocks_in_storage

    def get_context_data(self, **kwargs):
        self.base_title = 'Stocks / ' + self.object.name
        context = super().get_context_data(**kwargs)
        context['breadcrumbs'] = self.get_breadcrumbs()

        storage_page = self.request.GET.get('storage_page', default=1)
        storage_paginator = Paginator(Storage.objects.filter(parent_storage=self.object), self.default_pagination_size)
        stock_search_input = self.request.GET.get('search')

        componente_stock_page = self.request.GET.get('stock_page', default=1)
        
        stock_paginator = Paginator(self.search_stock_queryset(stock_search_input), self.default_pagination_size)

        context['storages'] = storage_paginator.get_page(storage_page)
        stocks = stock_paginator.get_page(componente_stock_page)
        context['stocks'] = stocks
        context['stock_search'] = stock_search_input
        add_storage_form = AddSubStorageForm()
        add_storage_form.fields['responsible'].initial = self.request.user.username
        context['add_storage_form'] = add_storage_form
        context['delete_storage_error'] = None
        context['add_stock_form'] = AddStockForm()

        return context

    def handle_add_storage_post(self, request, **kwargs):
        f = AddSubStorageForm(data=request.POST)
        if f.is_valid():
            sub_name = f.cleaned_data['storage_name']
            try:
                user = User.objects.get(username=f.cleaned_data['responsible'])
                try:
                    Storage.objects.create(name=sub_name, parent_storage=self.object, responsible=user)
                except ValidationError as v_err:
                    f.add_error('storage_name', '. '.join(v_err.messages))
            except:
                f.add_error('responsible', 'Invalid user')
        context = self.get_context_data(**kwargs)
        context['add_storage_form'] = f
        return self.render_to_response(context)

    def handle_del_storage_post(self, request, **kwargs):
        parent = self.object.parent_storage
        try:   
            self.object.delete()
        except:
            context = self.get_context_data(**kwargs)
            context['delete_storage_errors'] = ['Error deleting Storage '+str(self.object)]
            return self.render_to_response(context)
        
        if parent is None:
            return redirect('parts-stocks')
        else:
            return redirect(reverse('parts-stocks-detail', kwargs={'uuid':parent.id}))
    def handle_del_stock_post(self, request, **kwargs):
        del_error = None
        if 'stock_uuid' in request.POST:
            f = DeleteStockForm(data=request.POST)
            if f.is_valid():
                try:
                    s = Stock.objects.get(id=f.cleaned_data['stock_uuid'])
                    print(s.storage)
                    print(self.object)
                    if s.storage == self.object:
                       s.delete()
                    else:
                        del_error = 'Cannot delete stock from another storage.' 
                except:
                    del_error = 'Could not find requested stock in this storage.'
        
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)
        
    def handle_update_watermark(self, request, **kwargs):
        edit_form = EditWatermarkForm(data=request.POST)
        update_watermark_error = None
        if edit_form.is_valid():
            edit_form.save()
        else:
            pass # Todo: Handle error

        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)
    
    def handle_amount_change_post(self, request, increase, **kwargs):
        edit_form = EditStockAmountForm(data=request.POST)
        if edit_form.is_valid():
            edit_form.save(increase)

        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

    def handle_add_stock_post(self, request, **kwargs):
        f = AddStockForm(data=request.POST)
        error_occured = False

        if f.is_valid():
            try:
                f.save(self.object)
            except Exception as ex:
                f.add_error('', str(ex))
                error_occured = True
        else:
            error_occured = True

        context = self.get_context_data(**kwargs)
        if error_occured:
            context['add_stock_form'] = f
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()

        if 'submit-add-storage' in request.POST:
            return self.handle_add_storage_post(request, **kwargs)
        elif 'submit-delete-storage' in request.POST:
            return self.handle_del_storage_post(request, **kwargs)
        elif 'submit-delete-stock' in request.POST:
            return self.handle_del_stock_post(request, **kwargs)
        elif 'submit-edit-watermark' in request.POST:
            return self.handle_update_watermark(request, **kwargs)
        elif 'submit-amount-reduce' in request.POST:
            return self.handle_amount_change_post(request, False, **kwargs)
        elif 'submit-amount-increase' in request.POST:
            return self.handle_amount_change_post(request, True, **kwargs)     
        elif 'submit-add-stock' in request.POST:
            return self.handle_add_stock_post(request, **kwargs)

        return super().post(request, *args, **kwargs)

class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
    template_name = 'parts/components-detail.html'
    model = Component
    pk_url_kwarg = 'uuid'
    base_title = ''
    navbar_selected = 'Components'

    def prepare_initial_param_formset_data(self):
        parameters = ComponentParameter.objects.filter(component=self.object)
        initdata = []
        for param in parameters:
            param_type = param.parameter_type.parameter_type
            if param_type == 'F':
                value = param.text_value
            else:
                value = param.value
            initdata.append({'parameter_type': param.parameter_type, 'value': value})
        return initdata
    
    def get_context_data(self, **kwargs):
        self.base_title = 'Component / '+self.object.name
        context = super().get_context_data(**kwargs)
        context['component'] = self.object
        context['edit_form'] = EditComponentForm(instance=self.object)

        context['stocks'] = Stock.objects.filter(component=self.object)
        context['comp_form'] = ComponentForm()

        return context

    def handle_submit_edit_post(self, request, **kwargs):
        form_error = False

        form = EditComponentForm(instance=self.object, data=request.POST, files=request.FILES)
        if form.is_valid():
            try:
                form.save()
            except IntegrityError as ie:
                form.add_error('name', 'Component name, package, and manufacturer are not unique!')
                form.add_error('package', 'Component name, package, and manufacturer are not unique!')
                form.add_error('manufacturer', 'Component name, package, and manufacturer are not unique!')
                form_error = True
            self.object = self.get_object()
        else:
            form_error = True

        context = self.get_context_data(**kwargs)
        if form_error:
            context['edit_form'] = form
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if 'submit-edit-comp' in request.POST:
            return self.handle_submit_edit_post(request, **kwargs)
        
        return super().post(request, *args, **kwargs)

class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
    template_name = 'parts/packages-detail.html'
    model = Package
    pk_url_kwarg = 'uuid'
    base_title = ''
    navbar_selected = 'Packages'
    
    def get_context_data(self, **kwargs):
        self.base_title = 'Package / '+self.object.name
        context = super().get_context_data(**kwargs)
        context['package'] = self.object
        context['edit_form'] = PackageForm(instance=self.object)
 
        return context

    def handle_delete_package(self, request):
        delete_error = None
        protected_objects = None
        
        # Try to delete this instance
        try:
            self.object.delete()
        except ProtectedError as pe:
            delete_error = 'Cannot delete this package. It is referenced by a component.'
            protected_objects = pe.protected_objects
        except:
            delete_error = 'Cannot delete this package. Unknown error'
        
        if delete_error:
            context = self.get_context_data()
            context['delete_error'] = delete_error
            context['protected_components'] = protected_objects
            return self.render_to_response(context)
        else:
            return redirect('parts-packages')

    def edit_package(self, request):
        edit_form = PackageForm(data=request.POST, files=request.FILES, instance=self.object)

        if edit_form.is_valid():
            edit_form.save()

        context = self.get_context_data()
        if not edit_form.is_valid():
            context['edit_form'] = edit_form
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()

        if 'submit-pkg-delete' in request.POST:
            return self.handle_delete_package(request)
        elif 'submit-pkg-edit' in request.POST:
            return self.edit_package(request)
        
        return super().post(request, *args, **kwargs)

class DistributorDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView):
    template_name = 'parts/distributors-detail.html'
    model = Distributor
    pk_url_kwarg = 'uuid'
    base_title = ''
    navbar_selected = 'Distributors'
    
    def get_context_data(self, **kwargs):
        self.base_title = 'Distributor / '+self.object.name
        context = super().get_context_data(**kwargs)
        context['distributor'] = self.object
        context['edit_form'] = DistributorForm(instance=self.object)
 
        return context

    def handle_delete_distributor(self, request):
        delete_error = None
        protected_objects = None
        
        # Try to delete this instance
        try:
            self.object.delete()
        except ProtectedError as pe:
            delete_error = 'Cannot delete this distributor. It is referenced by a component.'
            protected_objects = pe.protected_objects
        except:
            delete_error = 'Cannot delete this distributor. Unknown error'
        
        if delete_error:
            context = self.get_context_data()
            context['delete_error'] = delete_error
            context['protected_components'] = protected_objects
            return self.render_to_response(context)
        else:
            return redirect('parts-distributors')

    def edit_distributor(self, request):
        edit_form = DistributorForm(data=request.POST, files=request.FILES, instance=self.object)

        if edit_form.is_valid():
            edit_form.save()

        context = self.get_context_data()
        if not edit_form.is_valid():
            context['edit_form'] = edit_form
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()

        if 'submit-distri-delete' in request.POST:
            return self.handle_delete_distributor(request)
        elif 'submit-distri-edit' in request.POST:
            return self.edit_distributor(request)
        
        return super().post(request, *args, **kwargs)

class ManufacturersViewSet(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
    template_name = 'parts/manufacturers.html'
    base_title = 'Manufacturers'
    navbar_selected = 'Manufacturers'
    default_page_size = 25

    def search_manufacturers(self, search):
        qs = Manufacturer.objects.all()

        if not search:
            return qs
        
        search_fragements = search.strip().split()

        for search in search_fragements:
            qs = qs.filter(Q(name__icontains = search) | Q(website__icontains = search))

        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        page_num = self.request.GET.get('page', default=1)
        search_string = self.request.GET.get('search', default=None)
        
        queryset = self.search_manufacturers(search_string)

        paginator = Paginator(queryset, self.default_page_size)       


        context['search_string'] = search_string
        context['manufacturers'] = paginator.get_page(page_num)
        context['new_manufacturer_form'] = ManufacturerForm()

        return context
    
    def handle_add_new_manufacturer(self, request):

        form = ManufacturerForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            form.save()

        context = self.get_context_data()

        if not form.is_valid():
            context['new_manufacturer_form'] = form

        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        if 'submit-manufacturer-add-new' in request.POST:
            return self.handle_add_new_manufacturer(request)

        return super().post(request, *args, **kwargs)

class ManufacturerDetailViewSet(LoginRequiredMixin, BaseTemplateMixin, DetailView):
    template_name = 'parts/manufacturers-detail.html'
    model = Manufacturer
    pk_url_kwarg = 'uuid'
    base_title = ''
    navbar_selected = 'Manufacturers'
    
    def get_context_data(self, **kwargs):
        self.base_title = 'Manufacturer / '+self.object.name
        context = super().get_context_data(**kwargs)
        context['manufacturer'] = self.object
        context['edit_form'] = ManufacturerForm(instance=self.object)
 
        return context

    def handle_delete_manufacturer(self, request):
        delete_error = None
        protected_objects = None
        
        # Try to delete this instance
        try:
            self.object.delete()
        except ProtectedError as pe:
            delete_error = 'Cannot delete this distributor. It is referenced by a component.'
            protected_objects = pe.protected_objects
        except:
            delete_error = 'Cannot delete this distributor. Unknown error'
        
        if delete_error:
            context = self.get_context_data()
            context['delete_error'] = delete_error
            context['protected_components'] = protected_objects
            return self.render_to_response(context)
        else:
            return redirect('parts-manufacturers')

    def edit_manufacturer(self, request):
        edit_form = ManufacturerForm(data=request.POST, files=request.FILES, instance=self.object)

        if edit_form.is_valid():
            edit_form.save()

        context = self.get_context_data()
        if not edit_form.is_valid():
            context['edit_form'] = edit_form
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()

        if 'submit-manufacturer-delete' in request.POST:
            return self.handle_delete_manufacturer(request)
        elif 'submit-manufacturer-edit' in request.POST:
            return self.edit_manufacturer(request)
        
        return super().post(request, *args, **kwargs)