from collections import defaultdict
from dataclasses import dataclass
from difflib import HtmlDiff, SequenceMatcher
from django import forms
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.views.generic.edit import UpdateView, CreateView
from django.views.generic.detail import DetailView

from atlas.models import Subspecialty

from .models import Entry, Formats

# Create your views here.
from .tables import EntryTable
from .filters import EntryFilter
from django_filters.views import FilterView
from django.contrib.auth.decorators import user_passes_test

from django_tables2 import SingleTableView, SingleTableMixin


class EntryUpdateView(UpdateView):
    model = Entry
    template_name = "oef/entry_update_view.html"
    fields = ["modality", "exam_code", "exam_name", "questions", "comments", "inactive"]


class EntryDetailView(DetailView):
    model = Entry
    template_name = "oef/entry_detail_view.html"


class FormatsCreateView(CreateView):
    model = Formats
    template_name_suffix = "_update_form"
    fields = ["name", "format", "inherits"]


class FormatsForm(forms.ModelForm):
    class Meta:
        model = Formats
        fields = ["name", "format", "specialties", "inherits"]
        widgets = {"specialties": forms.CheckboxSelectMultiple()}


class FormatsUpdateView(UpdateView):
    model = Formats
    template_name_suffix = "_update_form"
    # fields = ['name', 'format', 'specialties', 'inherits']
    form_class = FormatsForm


class EntryTableView(SingleTableMixin, FilterView):
    model = Entry
    table_class = EntryTable

    template_name = "oef/entry_table.html"

    filterset_class = EntryFilter


@dataclass
class FormatOutput:
    name: str
    action: str = ""
    field_seq: int = 100
    field_label: str = ""
    field_name: str = ""
    field_status: str = ""
    field_type: str = ""
    field_codeset: str = ""


def output(request):
    formats = Formats.objects.all()

    formats = sorted(formats, key=lambda x: len(x.format))

    outputs = []

    codesets = []
    codeset_start = 100000

    for format in formats:
        seq = 100
        questions = format.format.strip().split("---")

        for question in questions:
            if not question:
                continue

            name, *other = question.strip().splitlines()

            status = "OPTIONAL"
            if name.endswith("*"):
                status = "REQUIRED"
                name = name[:-1]

            field_type = ""
            match other:
                case ["Yes", "No"]:
                    field_type = "YES/NO"
                case ["Date/Time"]:
                    field_type = "DATE/TIME"
                case ["Freetext"]:
                    field_type = "ALPHANUMERIC"
                case [*codeset]:
                    field_type = "CODESET"
                case _:
                    field_type = "unknown"

            o = FormatOutput(
                format.name,
                field_label=name,
                field_name=name,
                field_seq=seq,
                field_status=status,
                field_type=field_type,
            )

            if field_type == "CODESET":
                o.field_codeset = codeset_start

                codesets.append((codeset_start, other))

                codeset_start = codeset_start + 100

            outputs.append(o)

            seq = seq + 100

    return render(
        request,
        "oef/output.html",
        {"formats": formats, "outputs": outputs, "codesets": codesets},
    )


def formats_view(request):
    formats = Formats.objects.all()
    return render(request, "oef/formats_view.html", {"formats": formats})

def formats_dupe_view(request):
    formats = Formats.objects.all()

    format_dict = defaultdict(list)

    dupes = []

    for format in formats:
        if format.format in format_dict:
            dupes.append((format, format_dict[format.format]))
        format_dict[format.format].append(format)

    
        

    return render(request, "oef/formats_dupe_view.html", {"dupes": dupes, "format_dict": format_dict})


@user_passes_test(lambda u: u.is_superuser)
def formats_delete(request, pk):
    f = get_object_or_404(Formats, pk=pk)
    f.delete()

    return HttpResponse("Format deleted")


def formats_apply(request, pk):
    format = get_object_or_404(Formats, pk=pk)

    search_text = format.get_format()

    entries = Entry.objects.filter(questions__icontains=search_text, inactive=False)

    entry_list = []
    for entry in entries:
        entry_list.append((entry, entry.questions.replace(search_text, "")))
    return render(
        request,
        "oef/formats_apply.html",
        {"format": format, "entry_list": entry_list, "entries": entries},
    )


def entry_detail_formats(request, pk):
    entry = get_object_or_404(Entry, pk=pk)
    formats = Formats.objects.all()

    questions = entry.questions

    matching_formats = []
    for format in formats:
        if format.get_format() in questions:
            matching_formats.append(format)

    matching_formats = sorted(matching_formats, key=lambda x: len(x.get_format()))

    return render(
        request,
        "oef/entry_detail_formats.html",
        {"entry": entry, "matching_formats": matching_formats},
    )


def replace_questions(request):
    if request.method == "POST":
        search = request.POST.get("search")
        replace = request.POST.get("replace")

        if not search or not replace:
            return HttpResponse("Please provide both search and replace values")

        if request.POST.get("exact", False):
            entries = Entry.objects.filter(questions=search, inactive=False)
            formats = Formats.objects.filter(format=search)
        else:
            entries = Entry.objects.filter(questions__contains=search, inactive=False)
            formats = Formats.objects.filter(format__contains=search)

        for entry in entries:
            entry.questions = entry.questions.replace(search, replace)
            entry.save()
        for format in formats:
            format.format = format.format.replace(search, replace)
            format.save()

        return HttpResponse(f"{entries.count()} questions replaced")
    else:
        return render(request, "oef/replace_questions.html", {})

def entries_table(request):
    entries = Entry.objects.filter(inactive=False).order_by("exam_code")
    return render(request, "oef/entries_table.html", {"entries": entries})

def entries(request):
    entries = Entry.objects.all().order_by("exam_code")
    return render(request, "oef/entries.html", {"entries": entries})


def format_search(request):
    if request.method == "POST":
        search = request.POST.get("search")

        entries = Entry.objects.filter(questions__icontains=search, inactive=False)

        return render(request, "oef/format_search_results.html", {"entries": entries})
    return render(
        request,
        "oef/format_search.html",
    )


def subspecialties(request):
    subspecialties = Subspecialty.objects.all()

    format_map = []
    for s in subspecialties:
        format_map.append((s, s.formats_set.all()))

    format_map.append(
        ("Nuclear Medicine", Formats.objects.filter(name__startswith="NM"))
    )

    return render(
        request, "oef/subspecialties.html", context={"format_map": format_map}
    )


def questions(request):
    questions = set(
        Entry.objects.filter(inactive=False).values_list("questions", flat=True)
    )

    questions = sorted(questions, key=lambda x: len(x))

    questions_and_formats = []

    for question in questions:
        try:
            format = Formats.objects.get(format=question)
        except Formats.DoesNotExist:
            format = None
        except Formats.MultipleObjectsReturned:
            format = Formats.objects.filter(format=question)

            return HttpResponse(f"Multiple (duplicate) formats found [{",".join([f.name for f in format])}]")
        questions_and_formats.append((question, format))

    return render(
        request,
        "oef/questions.html",
        {"questions": questions, "questions_and_formats": questions_and_formats},
    )


def entries_by_question(request):

    if request.method == "POST":
        question = request.POST.get("question")
        entries = Entry.objects.filter(questions=question, inactive=False)

        return render(request, "oef/entries_by_question.html", {"entries": entries})

    return HttpResponse("Please provide a question")


def entries_by_question_codes(request):

    if request.method == "POST":
        question = request.POST.get("question")
        entries = Entry.objects.filter(questions=question, inactive=False)

        return render(
            request, "oef/entries_by_question_codes.html", {"entries": entries}
        )

    return HttpResponse("Please provide a question")


def questions_diff(request):
    if request.method == "POST":
        question1 = request.POST.get("question1", False)
        question2 = request.POST.get("question2", False)

        if not question1:
            question1 = request.POST.get("search")
            question2 = request.POST.get("replace")

        html = HtmlDiff().make_table(question1.split("\n"), question2.split("\n"))

        return HttpResponse(html)

    return HttpResponse("Please provide two questions")


def questions_sym(request):
    if request.method == "POST":
        question = request.POST.get("question")

        questions = set(
            Entry.objects.filter(inactive=False).values_list("questions", flat=True)
        )

        output = []
        for q in questions:
            ratio = SequenceMatcher(None, question, q).ratio()

            if ratio > 0.5:
                output.append((q, ratio))

        output = sorted(output, key=lambda x: x[1], reverse=True)

        return render(request, "oef/questions_sym.html", {"output": output})

    return HttpResponse("Please provide a question set")


def formats_save(request):

    if request.method == "POST":
        question = request.POST.get("question")
        format_name = request.POST.get("format_name")

        if not question:
            return HttpResponse("Please provide a question")

        if Formats.objects.filter(name=format_name).exists():
            return HttpResponse("Format name already exists")

        if Formats.objects.filter(format=question).exists():
            return HttpResponse("Format questions already exists")

        Formats.objects.create(name=format_name, format=question)

        return HttpResponse("Format saved")

    return HttpResponse("Please provide a question")
