from django.contrib import messages
from datetime import datetime
from django.core.paginator import Paginator
from django.http.response import HttpResponse
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.core.files.storage import FileSystemStorage
from django.views.generic import ListView, UpdateView
from django.http import JsonResponse
from django.core.mail import EmailMessage
from django.db import transaction
from .cart import Cart
from .models import (
    Brand,
    CarModel,
    Engine,
    Car,
    Service,
    PriceList,
    Order,
    Csv,
    TRANSMISSION_CHOICES,
    FUEL_CHOICES,
)
from .forms import CsvUploadForm, OrderReleaseForm, UploadFileForm
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import user_passes_test, login_required
from django.template.loader import render_to_string
from drivers.settings import BASE_DIR
import csv, os

class SearchEngine(ListView):
    model = PriceList
    template_name = "main/search_price.html"
    paginate_by = 10
    return_rooms = None
    
    def search(self):
        prices = self.model.objects.filter(active=True)
        q = self.request.GET['brand']
        if q == "-":
            return []
        prices = prices.filter(car__brand__name__icontains=q)
            
        q = self.request.GET['model']
        if q == "-":
            return []
        prices = prices.filter(car__model__name__icontains=q)
        
        q = self.request.GET['engine']
        if q == "-":
            return []
        prices = prices.filter(car__engine__name__icontains=q)
        
        q = self.request.GET['fuel']
        if q == "-":
            return []
        prices = prices.filter(car__fuel=q)
        
        q = self.request.GET['transmission']
        if q == "-":
            return []
        prices = prices.filter(car__transmission=q)
        
        return prices
        
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['brands_all'] = Brand.objects.all()
        context['engines_all'] = Engine.objects.all()
        context['fuel_all'] = [item[0] for item in FUEL_CHOICES]
        context['transmission_all'] = [item[0] for item in TRANSMISSION_CHOICES]
        
        cart = Cart(self.request)

        context['cart_products'] = cart.get_prods()
        
        filters = ('brand', 'model', 'engine', 'fuel', 'transmission')
        prices = []
        context['object_list'] = prices
        
        if self.request.method == "GET":
            if self.request.GET['brand'] != '-':
                    context['models_all'] = []
                    for car in Car.objects.filter(brand__name=self.request.GET['brand']):
                        if not car.model in context['models_all']:
                            context['models_all'].append(car.model)
            try:
                for filter in filters:
                    value = self.request.GET[filter]
                    if value == "-":
                        messages.warning(self.request, "Musisz użyć wszystkich filtrów.")
                        return context
            except:
                pass
            else:
                prices = self.search()
                context['brand'] = self.request.GET['brand']
                context['model'] = self.request.GET['model']
                context['engine'] = self.request.GET['engine']
                context['fuel'] = self.request.GET['fuel']
                context['transmission'] = self.request.GET['transmission']
            
        page = self.request.GET.get('page', 1)
        paginator = Paginator(prices, self.paginate_by)
        prices = paginator.page(page)
        context['object_list'] = prices
        
        return context

def is_ajax(request):
    return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
   
def get_models_data(request, pk):
    if is_ajax(request):
        try:
            brand = Brand.objects.get(pk=pk)
        except Brand.DoesNotExist:
            messages.error(request, "Wybrana marka nie istnieje!")
            return redirect('main:search-view')
        
        models = []
        for car in Car.objects.filter(brand=brand):
            if not car.model.name in models:
                models.append(car.model.name)
        return JsonResponse({'models': models})

def cart_summary(request):
    cart = Cart(request)
    cart_products = cart.get_prods()
    total = cart.total()
    return render(request, "main/cart_summary.html", {'cart_products': cart_products, 'total': total})

def cart_quantity(request):
    cart = Cart(request)
    return JsonResponse({'quantity_all': cart.get_all_quantity()})

def cart_add(request):
    cart = Cart(request)
    if request.POST.get('action') == 'post':    
        product_id = int(request.POST.get('product_id'))
        price = get_object_or_404(PriceList, id=int(product_id))
        # sprawdzanie czy wybrany pojazd nie różni się od tego w koszyku
        cart_products = cart.get_prods()
        for product in cart_products:
            if price.car.brand.name != product.car.brand.name or price.car.model.name != product.car.model.name or price.car.engine.name != product.car.engine.name or price.car.fuel != product.car.fuel or price.car.transmission != product.car.transmission:
                messages.warning(request, f'Możesz mieć w koszyku tylko jeden samochód jednocześnie.<br>Nadal możesz wybrać inne modyfikacje do <b>{cart_products[0].car.brand.name} {cart_products[0].car.model.name} {cart_products[0].car.engine.name} | {cart_products[0].car.fuel} {cart_products[0].car.transmission}</b>')
                return JsonResponse({'wrong_car': True})
                
        cart.change(product=price, quantity=1)
        return JsonResponse({'quantity_all': cart.get_all_quantity(), 'wrong_car': False})
    
def cart_delete(request):
    cart = Cart(request)
    if request.POST.get('action') == 'post':
        product_id = int(request.POST.get('product_id'))
        cart.delete(product=product_id)
        return JsonResponse({'total': cart.total(), 'quantity_all': cart.get_all_quantity()})
    
def get_cart_items(request):
    if is_ajax(request) and request.POST.get('action') == 'post':
        cart = Cart(request)
        cart_products = cart.get_prods()
        items = [(price.car.brand.name, price.car.model.name, price.service.name, price.price, price.pk) for price in cart_products]
        return JsonResponse({'items': items})

@login_required(login_url='users:login') # tylko zalogowani
def order(request):
    form = UploadFileForm()
    if request.method == "POST":
        if request.POST.get('products'):
            products_ids = [int(pk) for pk in request.POST.getlist('products')]
            total = 0
            prices =  PriceList.objects.filter(pk__in=products_ids)
            for price in prices:
                total += price.price
            if request.user.profile.credits < total:
                messages.error(request, "Nie masz wystarczającej liczby credits!")
                return redirect('main:cart')
            return render(request, "main/order.html", {'products': prices, 'upload_form': form})
        elif request.FILES:
            form = UploadFileForm(request.POST or None, request.FILES or None)
            with transaction.atomic():
                prices = []
                for key in request.POST.keys():
                    if key == 'csrfmiddlewaretoken' or key == 'user_comment':
                        continue
                    prices.append(get_object_or_404(PriceList, id=int(key)))
                    
                if form.is_valid():
                    order = form.save()
                    order.user = request.user
                    fs = FileSystemStorage()
                    filename = fs.save(f'org_files/{request.FILES["org_file"].name.replace(" ",  "-")}', request.FILES["org_file"])
                    order.org_file = filename
                    
                    for price in prices:
                        order.price.add(price)
                    
                    order.save()
            
                    message = render_to_string("main/order_email.html", {
                            'user': request.user,
                            'order': order,
                        })
                    email = EmailMessage(
                        f'Zamówienie {order.date.strftime("%d/%m/%Y %H:%I")}', message, to=[request.user.email]
                        )
                    try:
                        if not email.send():
                            print("Błąd wysyłania wiadmości")
                    except:
                        print("Błąd wysyłania wiadmości")
                        
                    if request.user.profile.credits < order.total_price:
                        for files in order.files.all():
                            files.delete()
                        order.delete()
                        messages.error(request, "Nie masz wystarczającej liczby credits!")
                    else:
                        cart = Cart(request)
                        cart.clear()
                        request.user.profile.credits -= order.total_price
                        request.user.profile.save()
                else:
                    return render(request, "main/order.html", {'products': prices, 'upload_form': form})

    return redirect('main:cart')

@login_required(login_url='users:login') # tylko zalogowani
def buy_credits(request):
    request.user.profile.credits += 20
    request.user.profile.save()
    return redirect('users:profile')

@login_required(login_url='users:login') # tylko zalogowani
def download_file(request, pk, type="org"):
    try:
        order = Order.objects.get(pk=pk)
    except Order.DoesNotExist:
        messages.error(request, "Nie znaleziono zamówienia!")
        return redirect('users:profile')
    
    if order.user == request.user or request.user.is_staff:
        if type == "org":
            filepath = order.org_file.url
        elif type == "mod":
            filepath = order.mod_file.url
        print(os.path.abspath(BASE_DIR))
        with open(f'{BASE_DIR}{os.path.normpath(filepath)}', 'r', errors="ignore") as response_file:
            response = HttpResponse(response_file, content_type='multipart/form-data;')
            response['Content-Disposition'] = f'attachment; filename={os.path.basename(filepath)}'
            
            return response
    else:
        messages.error(request, "Nie masz uprawnień do pobrania tego pliku!")
        return redirect('users:profile')
    
@login_required(login_url='users:login') # tylko zalogowani
def show_order(request, pk):
    try:
        order = Order.objects.get(pk=pk)
    except Order.DoesNotExist:
        messages.error(request, "Wybrane zamówienie nie istnieje!")
        return redirect('users:profile')
    
    if request.user == order.user:
        return render(request, "main/show_order.html", {'order': order})
    else:
        messages.error(request, "Nie masz dostępu do tego zamówienia!")
        return redirect('users:profile')

## ADMIN   
class ReleaseOrder(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Order
    template_name = "main/release_order.html"
    form_class = OrderReleaseForm

    def test_func(self):
        print("test")
        self.login_url='users:login'
        if not self.request.user.is_staff:
            messages.error(self.request, ("Musisz mieć uprawnienia obsługi."))  
        return self.request.user.is_staff
    
    def handle_no_permission(self):
        return redirect('main:search-view')

    def form_valid(self, form):
        order = self.get_object()
        if self.request.FILES["file"]:
            fs = FileSystemStorage()
            filename = fs.save(f'mod_files/{self.request.FILES["file"].name.replace(" ",  "-")}', self.request.FILES["file"])
            order = filename
        
        order.status = self.request.POST['status']
        order.save()
        messages.success(self.request, ("Zapisano zmiany."))
        return redirect('main:release-order', order.pk)
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        order = self.get_object()
        context['order'] = order
        context['prices'] = order.price.all()
        return context

# dodawnie danych do bazy (NIE WIDOK)
# reupload bazy danych polega na dodaniu nowych marek, modeli, silników i usług
# oraz deaktywowaniu wszystkich istniejących cen (cena to też usługa, samochód itp.).
# Następnie aktywowane są wszytkie istniejące już w bazie ceny zgodnie z csv i
# dodawane nowe jeśli nie istnieją w bazie. Nieaktywne ceny nie wyświetlają się
# podczas wyszukiwania usług ale są widoczne w złożonych już zamówieniach.
@transaction.atomic
def csv_to_db(csv_object):
    # deaktywowanie wszystkich cen
    PriceList.objects.all().update(active=False)
    
    # czytanie pliku
    with open(csv_object.file.path, 'r', encoding="windows-1250") as csv_file:
        reader = csv.reader(csv_file) 
        for count, row in enumerate(reader):
            if count == 0: # pomijanie nagłówka
                continue
            
            row = row[0].split(';')
            brand = model = engine = car = service = None
            
            # Tworzenie Marki
            if row[0] != '':
                brand, _ = Brand.objects.get_or_create(
                    name=row[0].strip().capitalize(),
                    )
            # Tworzenie Modelu
            if row[1] != '':
                model, _ = CarModel.objects.get_or_create(
                    name=row[1].strip().capitalize(),
                    )
            # Tworzenie Silnika
            if row[2] != '':
                engine, _ = Engine.objects.get_or_create(
                    name=row[2].strip().capitalize(),
                    )
            
            # Tworzenie samochodu    
            if brand and model and engine and row[3] != '' and row[4] != '':
                car, _ = Car.objects.get_or_create(
                    brand=brand,
                    model=model,
                    engine=engine,
                    fuel=row[3].strip().capitalize(),
                    transmission=row[4].strip().capitalize(),
                )
            
            # Tworzenie usługi
            if row[5] != '':
                service, _ = Service.objects.get_or_create(
                    name=row[5].strip(),
                    )
                
            # Tworzenie ceny
            if car and service and row[6] != '':
                price, _ = PriceList.objects.get_or_create(
                    car=car,
                    service=service,
                    price=int(row[6]),
                    active=False,
                )
                price.active = True
                price.save()
            
    csv_object.activated = True
    csv_object.save()

@user_passes_test(lambda user: user.is_staff, login_url='users:login') # tylko admin i obsługa
def change_database(request):
    upload_form = CsvUploadForm(request.POST or None, request.FILES or None)

    if request.method == "POST" and upload_form.is_valid():
        upload_form.save()
        csv_object = Csv.objects.filter(activated=False).order_by('-upload_time')[0]
        
        try:
            csv_to_db(csv_object)
        except Exception as e:
            print(e)
            messages.error(request, (f'Błędny format danych w pliku!'))
            # csv_object.delete()
        else:
            messages.success(request, (f'Zamieniono zestaw w bazie danych.'))
        
        upload_form = CsvUploadForm()

    return render(request, 'main/reupload.html', {'csv_form': upload_form})

@user_passes_test(lambda user: user.is_staff, login_url='users:login') # tylko admin i obsługa
def download_database(request):
    prices = PriceList.objects.filter(active=True)
    # ścieżka pliku
    filepath = os.path.abspath(os.path.join(
        os.path.dirname(__file__), 
        os.pardir, 
        "media", 
        "csv_to_download", 
        "database_backup.csv"
        ))
    
    # tworzenie pliku
    with open(filepath, 'w', encoding="windows-1250", newline='') as response_file:
        writer = csv.writer(response_file, delimiter=';')
        writer.writerow(["Marka", "Model", "Silnik", "Paliwo", "Skrzynia", "Usługa", "Credits"])
        for price in prices:
            writer.writerow([
                price.car.brand.name,
                price.car.model.name,
                price.car.engine.name,
                price.car.fuel,
                price.car.transmission,
                price.service.name,
                price.price,
            ])
    
    # wysyłanie pliku
    with open(filepath, 'r', encoding="windows-1250") as response_file:
        response = HttpResponse(response_file, content_type='text/csv; charset=windows-1250')
        response['Content-Disposition'] = f'attachment; filename=ceny_baza_{datetime.now().strftime("%d-%m-%Y")}.csv'

    return response