import secrets
from typing import Any, Optional
import os
import re
from io import StringIO
from datetime import datetime
from django.conf import settings

from django_tables2 import SingleTableMixin
from atlas.models import CaseCollection, CidReportAnswer, Case as AtlasCase
from generic.decorators import user_is_cid_user_manager
from generic.filters import UserUserFilter
from generic.tables import UserUserTable
from generic.views import (
    CidManagerRequiredMixin,
    RedirectMixin,
    get_question_and_content_type,
    get_user_exams,
    get_exam_model_from_app_name,
)
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt
from django import forms

from django.core.exceptions import ValidationError
from django.core.validators import validate_email

from django.utils.html import format_html
from django.utils.safestring import mark_safe

# from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User

from django.contrib.contenttypes.models import ContentType

from django.contrib.auth.mixins import LoginRequiredMixin

from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView, TemplateView

from django.db.models.functions import Lower

from django.core.cache import cache

from django.urls import reverse_lazy, reverse

from django.http import Http404, JsonResponse

from django.http import HttpResponseRedirect, HttpResponse
from django.views.decorators.http import require_POST
from django.core.management import call_command, CommandError

from physics.models import UserAnswer as PhysicsUserAnswer
from physics.models import Exam as PhysicsExam

from anatomy.models import UserAnswer as AnatomyUserAnswer
from anatomy.models import Exam as AnatomyExam
from anatomy.models import AnatomyQuestion
from anatomy.models import Answer as AnatomyAnswer
from rad.filters import UserListFilter

from helpers.cimar import CimarAPI, InvalidCredentials, NoSession
from rapids.models import UserAnswer as RapidsUserAnswer
from rapids.models import Exam as RapidsExam
from rapids.models import Rapid
from rapids.models import Answer as RapidAnswer

from longs.models import UserAnswer as LongsUserAnswer
from longs.models import Exam as LongsExam
from longs.models import Long

from sbas.models import UserAnswer as SbasUserAnswer
from sbas.models import Exam as SbasExam

from anatomy.views import GenericExamViews as AnatomyExamViews
from rapids.views import GenericExamViews as RapidsExamsViews
from shorts.views import GenericExamViews as ShortsExamsViews
from longs.views import GenericExamViews as LongsExamsViews

from generic.forms import QuestionNoteForm
from generic.models import (
    CidUser,
    CimarCase,
    ExamCollection,
    QuestionNote,
    Supervisor,
    UserGrades,
    UserProfile,
    USER_EXAM_TYPES,
)
from django.contrib.auth.models import Group
from generic.models import UserUserGroup

from django_filters.views import FilterView

import json

from django.template import RequestContext

from django.db.models import Q

from dal import autocomplete

from loguru import logger
from .logging_setup import get_log_file_path

import psutil




def _media_file_inventory(path: str) -> dict[str, int]:
    """Return a mapping of file path to byte size for files under a directory."""
    if not path or not os.path.exists(path):
        return {}

    inventory: dict[str, int] = {}
    for root, _, files in os.walk(path):
        for filename in files:
            file_path = os.path.join(root, filename)
            try:
                inventory[file_path] = os.path.getsize(file_path)
            except OSError:
                continue

    return inventory


def feedback_checker(user):
    return user.groups.filter(name="feedback_checker").exists()


@user_passes_test(lambda u: u.is_superuser)
def server(request):
    return render(request, "server.html")


@user_is_cid_user_manager
def people(request):
    """Render the people management overview or search users via HTMX.

    Scope: managers and superusers.
    Functionality: provides details on user roles/groups, and a single search
    input that filters users dynamically via HTMX GET requests.
    """
    if request.htmx:
        q = request.GET.get("q", "").strip()
        if not q:
            return HttpResponse("")

        users = (
            User.objects.filter(
                Q(first_name__icontains=q)
                | Q(last_name__icontains=q)
                | Q(email__icontains=q)
                | Q(username__icontains=q)
            )
            .prefetch_related(
                "userprofile", "userprofile__grade", "userprofile__supervisor", "user_groups"
            )
            .order_by("first_name", "last_name", "username")[:20]
        )

        return render(
            request,
            "rad/partials/_people_search_results.html",
            {"users": users, "q": q},
        )

    return render(request, "people.html", {})



@login_required
def profile(request):
    """Render the current user's profile page with group management context.

    Scope: authenticated user's own profile view.
    Functionality: provides user details, available groups, and supervisor
    account state for template actions.
    """
    user = request.user
    available_groups = UserUserGroup.objects.filter(archive=False).exclude(
        pk__in=user.user_groups.values_list("pk", flat=True)
    )
    available_auth_groups = Group.objects.exclude(
        pk__in=user.groups.values_list("pk", flat=True)
    )
    is_cid_user_manager = request.user.groups.filter(name="cid_user_manager").exists()
    supervisor_account = Supervisor.objects.filter(user=user).first()
    return render(
        request,
        "profile.html",
        {
            "user": user,
            "available_groups": available_groups,
            "available_auth_groups": available_auth_groups,
            "is_cid_user_manager": is_cid_user_manager,
            "supervisor_account": supervisor_account,
            "supervisor_action": request.GET.get("supervisor_action", ""),
            "password_reset_action": request.GET.get("password_reset_action", ""),
        },
    )


def index(request):
    collections = []

    if request.user.is_authenticated:
        collections = ExamCollection.objects.filter(archive=False, author=request.user)

    rcr_assessor = request.user.groups.filter(
        Q(name="rcr_radiology_assessor")
        | Q(name="rcr_oncology_assessor")
        | Q(name="rcr_assessor")
    ).exists()
    return render(
        request,
        "index.html",
        {"rcr_assessor": rcr_assessor, "collections": collections},
    )


@user_is_cid_user_manager
def account_profile(request, slug):
    """Render an account profile page for administrators.

    Scope: cid_user_manager and superusers viewing another user's profile.
    Functionality: exposes profile details, group controls, and supervisor
    account creation/link status for admin actions.
    """
    user = get_object_or_404(User, username=slug)
    available_groups = UserUserGroup.objects.filter(archive=False).exclude(
        pk__in=user.user_groups.values_list("pk", flat=True)
    )
    available_auth_groups = Group.objects.exclude(
        pk__in=user.groups.values_list("pk", flat=True)
    )
    is_cid_user_manager = request.user.groups.filter(name="cid_user_manager").exists()
    supervisor_account = Supervisor.objects.filter(user=user).first()
    return render(
        request,
        "profile.html",
        {
            "user": user,
            "available_groups": available_groups,
            "available_auth_groups": available_auth_groups,
            "is_cid_user_manager": is_cid_user_manager,
            "supervisor_account": supervisor_account,
            "supervisor_action": request.GET.get("supervisor_action", ""),
            "password_reset_action": request.GET.get("password_reset_action", ""),
        },
    )


@require_POST
@user_is_cid_user_manager
def account_set_supervisor(request, slug):
    """Create or link a Supervisor record for an existing user.

    Scope: cid_user_manager and superusers only.
    Functionality: one-click action from profile pages to assign a user as a
    supervisor account, reusing an existing supervisor by email when safe.
    """
    user = get_object_or_404(User, username=slug)

    next_url = request.POST.get("next") or reverse("account_profile", kwargs={"slug": user.username})

    email = (user.email or "").strip().lower()
    if not email:
        return redirect(f"{next_url}?supervisor_action=no_email")

    existing_for_user = Supervisor.objects.filter(user=user).first()
    if existing_for_user:
        return redirect(f"{next_url}?supervisor_action=already_linked")

    supervisor = Supervisor.objects.filter(email__iexact=email).first()
    if supervisor:
        if supervisor.user and supervisor.user != user:
            return redirect(f"{next_url}?supervisor_action=email_in_use")

        supervisor.user = user
        if not supervisor.name:
            supervisor.name = user.get_full_name().strip() or user.username
        supervisor.save()
        return redirect(f"{next_url}?supervisor_action=linked")

    name = user.get_full_name().strip() or user.username
    Supervisor.objects.create(email=email, name=name, user=user)
    return redirect(f"{next_url}?supervisor_action=created")


@require_POST
@user_is_cid_user_manager
def account_send_password_reset(request, slug):
    """Send a password reset email for an existing user from their profile page."""
    user = get_object_or_404(User, username=slug)

    next_url = request.POST.get("next") or reverse("account_profile", kwargs={"slug": user.username})
    email = (user.email or user.username or "").strip()

    if not email:
        return redirect(f"{next_url}?password_reset_action=no_email")

    if not user.is_active:
        return redirect(f"{next_url}?password_reset_action=inactive")

    form = PasswordResetForm({"email": email})
    if not form.is_valid():
        return redirect(f"{next_url}?password_reset_action=invalid")

    try:
        form.save(
            request=request,
            use_https=request.is_secure(),
            from_email=getattr(settings, "DEFAULT_FROM_EMAIL", None),
        )
    except Exception:
        logger.exception("Failed to send password reset email for user {}", user.pk)
        return redirect(f"{next_url}?password_reset_action=error")

    return redirect(f"{next_url}?password_reset_action=sent")


@user_passes_test(lambda u: u.is_superuser)
def logs_view(request):
    """Display production logs with search/filter controls.
    
    Scope: superusers only.
    Functionality: reads the configured application log file and displays entries with optional text search filter.
    Supports ?all=1 to show all entries without pagination limit.
    """
    log_file_path = str(get_log_file_path())
    search_query = request.GET.get("q", "").strip()
    level_filter = request.GET.get("level", "").strip().upper()
    show_all = request.GET.get("all") == "1"
    raw_mode = request.GET.get("raw") == "1"
    include_noise = request.GET.get("include_noise") == "1"
    date_from_raw = request.GET.get("date_from", "").strip()
    date_to_raw = request.GET.get("date_to", "").strip()
    time_from_raw = request.GET.get("time_from", "").strip()
    time_to_raw = request.GET.get("time_to", "").strip()
    action_message = ""

    if request.method == "POST" and request.POST.get("action") == "clear":
        try:
            os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
            with open(log_file_path, "w", encoding="utf-8"):
                pass
            action_message = "Log file cleared."
        except Exception as exc:
            action_message = f"Failed to clear log file: {exc}"

    def _parse_log_timestamp(timestamp_text: str) -> datetime | None:
        if not timestamp_text:
            return None
        for timestamp_format in ("%d/%b/%Y %H:%M:%S", "%Y-%m-%d %H:%M:%S"):
            try:
                return datetime.strptime(timestamp_text, timestamp_format)
            except ValueError:
                continue
        return None

    def _parse_date_input(value: str):
        if not value:
            return None
        try:
            return datetime.strptime(value, "%Y-%m-%d").date()
        except ValueError:
            return None

    def _parse_time_input(value: str):
        if not value:
            return None
        for time_format in ("%H:%M", "%H:%M:%S"):
            try:
                return datetime.strptime(value, time_format).time()
            except ValueError:
                continue
        return None

    date_from = _parse_date_input(date_from_raw)
    date_to = _parse_date_input(date_to_raw)
    time_from = _parse_time_input(time_from_raw)
    time_to = _parse_time_input(time_to_raw)

    if raw_mode:
        if not os.path.exists(log_file_path):
            return HttpResponse(
                f"Log file not found at {log_file_path}\n",
                content_type="text/plain; charset=utf-8",
                status=404,
            )

        try:
            with open(log_file_path, "r", encoding="utf-8", errors="ignore") as f:
                raw_content = f.read()
        except Exception as e:
            return HttpResponse(
                f"Error reading log file: {str(e)}\n",
                content_type="text/plain; charset=utf-8",
                status=500,
            )

        return HttpResponse(raw_content, content_type="text/plain; charset=utf-8")

    log_header_re = re.compile(
        r"^\[(?P<timestamp>[^\]]+)\]\s+(?P<level>[A-Z]+)\s+\[(?P<logger>[^:\]]+):(?P<line>\d+)\]\s+(?P<message>.*)$"
    )

    parsed_entries: list[dict[str, Any]] = []
    if os.path.exists(log_file_path):
        try:
            with open(log_file_path, "r", encoding="utf-8", errors="ignore") as f:
                lines = f.readlines()

            current_entry = None
            for raw_line in lines:
                line = raw_line.rstrip("\n")
                if not line.strip():
                    if current_entry is not None:
                        current_entry["details"].append("")
                    continue

                header_match = log_header_re.match(line)
                if header_match:
                    if current_entry is not None:
                        parsed_entries.append(current_entry)

                    current_entry = {
                        "timestamp": header_match.group("timestamp"),
                        "level": header_match.group("level"),
                        "logger": header_match.group("logger"),
                        "source_line": header_match.group("line"),
                        "message": header_match.group("message"),
                        "details": [],
                    }
                    continue

                if current_entry is not None:
                    current_entry["details"].append(line)
                else:
                    parsed_entries.append(
                        {
                            "timestamp": "",
                            "level": "RAW",
                            "logger": "",
                            "source_line": "",
                            "message": line,
                            "details": [],
                        }
                    )

            if current_entry is not None:
                parsed_entries.append(current_entry)

        except Exception as e:
            parsed_entries = [
                {
                    "timestamp": "",
                    "level": "ERROR",
                    "logger": "logs_view",
                    "source_line": "",
                    "message": f"Error reading log file: {str(e)}",
                    "details": [],
                }
            ]
    else:
        parsed_entries = [
            {
                "timestamp": "",
                "level": "ERROR",
                "logger": "logs_view",
                "source_line": "",
                "message": f"Log file not found at {log_file_path}",
                "details": [],
            }
        ]

    # Most recent first.
    parsed_entries = list(reversed(parsed_entries))

    available_levels = sorted({entry["level"] for entry in parsed_entries if entry["level"]})

    filtered_entries = []
    search_lower = search_query.lower()
    noise_suppressed = 0
    for entry in parsed_entries:
        # Django template debug logs can be very noisy and usually do not indicate
        # fatal issues. Hide them by default, with an explicit opt-in toggle.
        is_template_debug_noise = (
            entry.get("level") == "DEBUG"
            and entry.get("logger") == "django.template"
            and (
                "Exception while resolving variable" in entry.get("message", "")
                or any("VariableDoesNotExist" in d for d in entry.get("details", []))
            )
        )
        if is_template_debug_noise and not include_noise:
            noise_suppressed += 1
            continue

        if level_filter and entry["level"] != level_filter:
            continue

        haystack = "\n".join(
            [
                entry.get("timestamp", ""),
                entry.get("level", ""),
                entry.get("logger", ""),
                entry.get("message", ""),
                "\n".join(entry.get("details", [])),
            ]
        ).lower()
        if search_lower and search_lower not in haystack:
            continue

        entry_timestamp = _parse_log_timestamp(entry.get("timestamp", ""))
        if date_from or date_to or time_from or time_to:
            if entry_timestamp is None:
                continue
            if date_from and entry_timestamp.date() < date_from:
                continue
            if date_to and entry_timestamp.date() > date_to:
                continue
            if time_from and entry_timestamp.time() < time_from:
                continue
            if time_to and entry_timestamp.time() > time_to:
                continue

        filtered_entries.append(entry)

    total = len(filtered_entries)
    limit = None if show_all else 500
    displayed_entries = filtered_entries[:limit] if limit else filtered_entries

    return render(
        request,
        "logs_viewer.html",
        {
            "entries": displayed_entries,
            "total_entries": total,
            "search_query": search_query,
            "level_filter": level_filter,
            "available_levels": available_levels,
            "log_file_path": log_file_path,
            "show_all": show_all,
            "limit": limit,
            "raw_url": f"{reverse('logs_view')}?raw=1",
            "include_noise": include_noise,
            "noise_suppressed": noise_suppressed,
            "date_from": date_from_raw,
            "date_to": date_to_raw,
            "time_from": time_from_raw,
            "time_to": time_to_raw,
            "action_message": action_message,
        },
    )


@login_required
@user_is_cid_user_manager
def media_cleanup(request):
    """Run django-unused-media cleanup from a manager-facing maintenance page.

    Scope: cid_user_manager group members and superusers.
    Functionality: supports safe preview (dry-run) and confirmed deletion runs,
    returning HTMX fragments for inline results.
    """
    context: dict[str, Any] = {
        "ran": False,
        "action": "preview",
        "minimum_file_age": 60,
        "remove_empty_dirs": True,
        "exclude": "",
    }

    if request.method == "POST":
        action = request.POST.get("action", "preview")
        dry_run = action != "cleanup"
        exclude_raw = (request.POST.get("exclude") or "").strip()
        exclude = [line.strip() for line in exclude_raw.splitlines() if line.strip()]
        remove_empty_dirs = request.POST.get("remove_empty_dirs") == "on"

        min_age_raw = (request.POST.get("minimum_file_age") or "60").strip()
        try:
            minimum_file_age = max(0, int(min_age_raw))
        except (TypeError, ValueError):
            minimum_file_age = 0

        out = StringIO()
        success = True

        command_kwargs: dict[str, Any] = {
            "dry_run": dry_run,
            "no_input": True,
            "interactive": False,
            "minimum_file_age": minimum_file_age,
            "remove_empty_dirs": remove_empty_dirs,
            "verbosity": 2,
            "stdout": out,
        }
        if exclude:
            command_kwargs["exclude"] = exclude

        error_message = ""
        media_inventory_before = _media_file_inventory(settings.MEDIA_ROOT)
        media_inventory_after = media_inventory_before
        try:
            call_command("cleanup_unused_media", **command_kwargs)
        except CommandError as exc:
            success = False
            error_message = str(exc)
        except Exception as exc:  # pragma: no cover
            success = False
            error_message = str(exc)

        media_inventory_after = _media_file_inventory(settings.MEDIA_ROOT)

        media_size_before = sum(media_inventory_before.values())
        media_size_after = sum(media_inventory_after.values())
        removed_files = set(media_inventory_before) - set(media_inventory_after)

        output = out.getvalue().strip()
        # Use before/after total delta so reported recovered space matches real disk impact.
        recovered_size = max(0, media_size_before - media_size_after)
        context.update(
            {
                "ran": True,
                "success": success,
                "action": action,
                "dry_run": dry_run,
                "minimum_file_age": minimum_file_age,
                "remove_empty_dirs": remove_empty_dirs,
                "exclude": exclude_raw,
                "output": output,
                "error_message": error_message,
                "media_size_before": media_size_before,
                "media_size_after": media_size_after,
                "recovered_size": recovered_size,
                "removed_file_count": len(removed_files),
            }
        )

        if request.htmx:
            return render(request, "rad/partials/_media_cleanup_result.html", context)

    return render(request, "rad/media_cleanup.html", context)


def cid_selector(request):
    return render(
        request,
        "cid_selector.html",
    )


@login_required
@user_passes_test(lambda u: u.is_superuser)
def cid_scores_admin(request, cid):
    cid_user = CidUser.objects.filter(cid=cid).first()

    if not cid_user:
        raise Http404("CID not found")

    return cid_user_overview(request, cid_user.cid, cid_user.passcode)


@login_required
# @user_passes_test(lambda u: u.is_superuser)
@user_is_cid_user_manager
def cid_results(request, cid):
    cid_user = CidUser.objects.filter(cid=cid).first()

    if not cid_user:
        raise Http404("CID not found")

    report = cid_user.generate_exam_report()

    return render(
        request,
        "cid_results.html",
        {
            "report": report,
        },
    )


@user_passes_test(lambda u: u.is_superuser)
def user_scores_admin(request, user_id):
    try:
        user = User.objects.get(id=user_id)
    except User.DoesNotExist:
        raise Http404("Cid not found")

    return user_scores(request, user=user)


@login_required
def user_scores(request, user=None):
    admin = True
    if user is None:
        user = request.user
        admin = False

    from django.contrib.contenttypes.models import ContentType
    from generic.models import UserHiddenItem

    show_hidden = request.GET.get("show_hidden") in ("true", "1")

    # Fetch user's hidden items map: {content_type_id: {object_ids}}
    hidden_map = {}
    for ct_id, obj_id in UserHiddenItem.objects.filter(user=user).values_list("content_type_id", "object_id"):
        hidden_map.setdefault(ct_id, set()).add(obj_id)

    # Filter Exam Results list (all_exams)
    exams = get_user_exams(user)
    filtered_exams = []
    for exam_type, exams_qs in exams:
        exam_model = exams_qs.model
        ct_id = ContentType.objects.get_for_model(exam_model).id
        hidden_ids = hidden_map.get(ct_id, set())

        if not show_hidden:
            exams_qs = exams_qs.exclude(id__in=hidden_ids)

        exams_list = list(exams_qs)
        for exam in exams_list:
            exam.is_hidden = exam.id in hidden_ids

        if exams_list:
            filtered_exams.append((exam_type, exams_list))

    # Filter Assigned Exams list (available_exams)
    available_exams = []
    for n, t in USER_EXAM_TYPES:
        exam_rel = getattr(user, t)
        if exam_rel.exists():
            temp_exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
            exam_model = temp_exams.model
            ct_id = ContentType.objects.get_for_model(exam_model).id
            hidden_ids = hidden_map.get(ct_id, set())

            if not show_hidden:
                temp_exams = temp_exams.exclude(id__in=hidden_ids)

            temp_exams_list = list(temp_exams)
            for exam in temp_exams_list:
                exam.is_hidden = exam.id in hidden_ids

            if temp_exams_list:
                available_exams.append((n, temp_exams_list))

    return render(
        request,
        "user_scores.html",
        {
            "all_exams": filtered_exams,
            "cid_user": user,
            "available_exams": available_exams,
            "case_collections": [],
            "admin": admin,
            "show_hidden": show_hidden,
        },
    )



@login_required
def user_marking(request):
    """Main page for user marking overview. Uses HTMX to fetch per-app partials."""
    apps = ["physics", "anatomy", "rapids", "shorts", "longs", "sbas"]
    return render(request, "rad/user_marking.html", {"apps": apps})


@login_required
def user_marking_partial(request, exam_type: str):
    """Return an HTMX partial listing exams the user can mark for a given exam type.

    Each exam is checked for whether it has any unmarked answers (using
    question.get_unmarked_user_answer_count when available).
    """
    user = request.user

    try:
        ExamModel = get_exam_model_from_app_name(exam_type)
    except Exception:
        return HttpResponse("Unknown exam type", status=400)

    # Exams where user is an author or an authorised marker
    exams_qs = (
        ExamModel.objects.filter(archive=False, exam_mode=True)
        .filter(Q(author=user) | Q(markers=user))
        .distinct()
        .order_by("name")
        .prefetch_related("author", "markers")
    )

    exams_data = []
    for exam in exams_qs:
        has_unmarked = False
        unmarked_count = 0
        # iterate questions and use their helper to count unmarked answers when available
        for q in exam.get_questions():
            count = 0
            # Prefer question-provided helper if present
            helper = getattr(q, "get_unmarked_user_answer_count", None)
            if callable(helper):
                try:
                    count = helper(exam_pk=exam.pk, marker=user)
                except TypeError:
                    count = helper(exam_pk=exam.pk)
                except Exception:
                    count = 0
            else:
                # Fallback: inspect related answer manager if present
                rel_manager = None
                for rel_name in ("cid_user_answers", "user_answers", "answers", "useranswer_set"):
                    if hasattr(q, rel_name):
                        rel_manager = getattr(q, rel_name)
                        break

                if rel_manager is not None:
                    try:
                        ans_model = rel_manager.model
                        # If the answer model has a `score` field, count answers that are unmarked
                        field_names = [f.name for f in ans_model._meta.get_fields()]
                        if "score" in field_names:
                            # Try to detect an UNMARKED constant on the model
                            unmarked_vals = [None, ""]
                            model_score_const = getattr(ans_model, "ScoreOptions", None) or getattr(ans_model, "MarkOptions", None)
                            if model_score_const is not None:
                                # attempt to use any UNMARKED attribute
                                unmarked_val = getattr(model_score_const, "UNMARKED", None)
                                if unmarked_val is not None:
                                    unmarked_vals.append(unmarked_val)

                            count = rel_manager.filter(exam__id=exam.pk).filter(score__in=unmarked_vals).count()
                        else:
                            # No score field — likely auto-marked; treat as 0
                            count = 0
                    except Exception:
                        count = 0

            try:
                c = int(count)
            except Exception:
                c = 0
            if c:
                has_unmarked = True
                unmarked_count += c

        exams_data.append({"exam": exam, "has_unmarked": has_unmarked, "unmarked_count": unmarked_count})

    return render(request, "generic/partials/user_marking_exam_list.html", {"exams_data": exams_data, "exam_type": exam_type})


def cid_user_overview(request, cid, passcode):
    # exam = get_object_or_404(Exam, pk=pk)

    cid_user = CidUser.objects.filter(cid=cid).first()
    if not cid_user or cid_user.passcode != passcode:
        raise Http404("CID / Passcode combination not found")

    EXAM_ANSWER_MAP = {
        "physics": (PhysicsUserAnswer, PhysicsExam),
        "anatomy": (AnatomyUserAnswer, AnatomyExam),
        "rapids": (RapidsUserAnswer, RapidsExam),
        "longs": (LongsUserAnswer, LongsExam),
        "sbas": (SbasUserAnswer, SbasExam),
    }
    kwargs = {"exam_mode": True, "archive": False}

    exams = []
    for exam_type in EXAM_ANSWER_MAP:
        UserAnswer, Exam = EXAM_ANSWER_MAP[exam_type]
        exam_answers = UserAnswer.objects.filter(cid=cid)
        exam_ids = exam_answers.values_list("exam").distinct()
        logger.debug(f"DEBUG cid_user_overview: {exam_type=}, {cid=}, answers_count={exam_answers.count()}, exam_ids={list(exam_ids)}")
        exams_to_add = Exam.objects.filter(id__in=exam_ids, **kwargs)
        logger.debug(f"DEBUG cid_user_overview: {exams_to_add=}")
        if exams_to_add:
            exams.append((exam_type, exams_to_add))

    case_collections = cid_user.casecollection_exams.all()

    available_exams = cid_user.get_cid_exams(include_case_collections=False)

    return render(
        request,
        "cid_user_overview.html",
        {
            # "physics_exams": physics_exams,
            # "anatomy_exams": anatomy_exams,
            # "rapid_exams": rapid_exams,
            # "longs_exams": longs_exams,
            # "sba_exams": sba_exams,
            "all_exams": exams,
            "cid": cid,
            "passcode": passcode,
            "cid_user": cid_user,
            "available_exams": available_exams,
            "case_collections": case_collections,
        },
    )


def active_exams(request, cid=None, passcode=None, based=True):
    active_exams = {}

    if cid is not None:
        active_exams["cid"] = cid
        cid_user = CidUser.objects.filter(cid=cid).first()
        if not cid_user or cid_user.passcode != passcode:
            return JsonResponse({"status": "invalid"}, status=401)

    anatomy_exams = AnatomyExamViews.active_exams(
        request, False, cid=cid, passcode=passcode
    )
    rapid_exams = RapidsExamsViews.active_exams(
        request, False, cid=cid, passcode=passcode
    )
    short_exams = ShortsExamsViews.active_exams(
        request, False, cid=cid, passcode=passcode
    )
    long_exams = LongsExamsViews.active_exams(request, False, cid=cid, passcode=passcode)

    exams = []
    exams.extend(anatomy_exams)
    exams.extend(rapid_exams)
    exams.extend(short_exams)
    exams.extend(long_exams)

    active_exams["exams"] = exams

    if request.user:
        active_exams["user"] = request.user.username
        active_exams["user_id"] = request.user.pk
    else:
        active_exams["user"] = False

    return JsonResponse(active_exams)


def active_exams_unbased(request, cid=None, passcode=None):
    return active_exams(request, cid, passcode, based=False)


def active_exams_by_type(request, exam_type, cid=None, passcode=None, based=True):
    active_exams_data = {}

    if cid is not None:
        active_exams_data["cid"] = cid
        cid_user = CidUser.objects.filter(cid=cid).first()
        if not cid_user or cid_user.passcode != passcode:
            return JsonResponse({"status": "invalid"}, status=401)

    exams = []
    if exam_type == "anatomy":
        exams = AnatomyExamViews.active_exams(
            request, False, cid=cid, passcode=passcode
        )
    elif exam_type == "rapid":
        exams = RapidsExamsViews.active_exams(
            request, False, cid=cid, passcode=passcode
        )
    elif exam_type == "short":
        exams = ShortsExamsViews.active_exams(
            request, False, cid=cid, passcode=passcode
        )
    elif exam_type == "long":
        exams = LongsExamsViews.active_exams(
            request, False, cid=cid, passcode=passcode
        )

    active_exams_data["exams"] = exams

    if request.user and request.user.is_authenticated:
        active_exams_data["user"] = request.user.username
        active_exams_data["user_id"] = request.user.pk
    else:
        active_exams_data["user"] = False

    return JsonResponse(active_exams_data)


def active_exams_by_type_unbased(request, exam_type, cid=None, passcode=None):
    return active_exams_by_type(request, exam_type, cid, passcode, based=False)


@csrf_exempt
def exam_submit(request):
    if request.method == "POST":
        exam_type, exam_id = request.POST.get("eid").split("/")

        try:
            if exam_type == "anatomy":
                return AnatomyExamViews.post_exam_answers(request)
            elif exam_type == "rapid":
                return RapidsExamsViews.post_exam_answers(request)
            elif exam_type == "long":
                return LongsExamsViews.post_exam_answers(request)
            elif exam_type == "short":
                return ShortsExamsViews.post_exam_answers(request)

            return JsonResponse({"success": False, "error": "Invalid exam type"})
        except ObjectDoesNotExist:
            return JsonResponse({"success": False, "error": "Invalid data"})

    # post_exam_answers
    return JsonResponse({"success": False, "error": "Invalid data"})


def feedback_create(request, question_type, pk):
    if question_type == "anatomy":
        question = AnatomyQuestion
    elif question_type == "rapid":
        question = Rapid
    elif question_type == "long":
        question = Long

    return JsonResponse({"success": False, "error": "Invalid exam type"})


class AddQuestionNote(CreateView):
    model = QuestionNote
    form_class = QuestionNoteForm

    # fields = ("author", "note_type", "note")

    def get_form_kwargs(self):
        kwargs = super(AddQuestionNote, self).get_form_kwargs()
        # update the kwargs for the form init method with yours
        kwargs.update(self.kwargs)  # self.kwargs contains all url conf params
        kwargs.update({"user": self.request.user})
        return kwargs

    def get_initial(self):
        pk = self.kwargs["pk"]
        self.question_type = self.kwargs["question_type"]

        question, content_type = get_question_and_content_type(self.question_type)

        self.question = get_object_or_404(question, pk=pk)
        return {"content_type": content_type, "object_id": pk}

    def get_context_data(self, **kwargs):
        # This can also be accessed via view.**** in the template
        context = super(AddQuestionNote, self).get_context_data(**kwargs)
        context["question"] = self.question
        context["question_type"] = self.question_type
        return context

    def form_valid(self, form):
        model = form.save(commit=False)

        if self.request.user.is_authenticated:
            model.user_author = self.request.user

        model.save()

        response = super().form_valid(form)

        return render(self.request, "feedback_submitted.html")
        return response

    def get_success_url(self) -> str:
        return super().get_success_url()


@user_passes_test(lambda u: u.is_superuser)
def feedback_note_delete(request, pk):
    q = get_object_or_404(QuestionNote, pk=pk)
    q.delete()

    return redirect(q.get_object_url())


@login_required
def feedback_mark_complete(request, pk):
    q = get_object_or_404(QuestionNote, pk=pk)

    if (
        request.user not in q.question.author.all()
        and not request.user.groups.filter(name="feedback_checker").exists()
    ):
        raise PermissionDenied()

    q.complete = True
    q.save()
    return redirect(q.get_object_url())


@login_required
def answer_suggestion_submit(request):
    """Submit a proposed answer to a question for review.

    Scope: authenticated users only.
    Functionality: accepts a JSON POST containing the question ID, proposed answer
    string, and status; creates a new Answer record marked as proposed=True for
    moderator review. Only 'anatomy' and 'rapid' question types are supported.
    """
    if request.method != "POST":
        return JsonResponse({"success": False, "error": "Invalid data"})

    post_data = json.loads(request.body)
    question_type, qid = post_data["qid"].split("/")
    answer_string = post_data["answer"]
    status = post_data["status"]

    if str(status) not in " 012":
        return JsonResponse({"success": False, "error": "Invalid status"})

    if question_type == "anatomy":
        AnswerModel = AnatomyAnswer
        question = AnatomyQuestion
    elif question_type == "rapid":
        AnswerModel = RapidAnswer
        question = Rapid
    else:
        return JsonResponse({"success": False, "error": "Invalid question type"})

    q = get_object_or_404(question, pk=qid)
    if AnswerModel.objects.filter(answer=answer_string, question=q):
        return JsonResponse({"success": False, "error": "Answer already exists"})

    a = AnswerModel(question=q, answer=answer_string, status=status, proposed=True)
    a.save()
    return JsonResponse({"success": True, "error": "Answer submitted"})


@login_required
def answer_submit(request):
    if request.method == "POST":
        qid = request.POST.get("qid")
        question_type = request.POST.get("question_type")
        answer_string = request.POST.get("answer")
        status = request.POST.get("status")

        if str(status) not in "012":
            return JsonResponse({"success": False, "error": "Invalid status"})

        if question_type == "anatomy":
            AnswerModel = AnatomyAnswer
            question = AnatomyQuestion
        elif question_type == "rapid":
            AnswerModel = RapidAnswer
            question = Rapid
        else:
            return JsonResponse({"success": False, "error": "Invalid question type"})

        q = get_object_or_404(question, pk=qid)
        if AnswerModel.objects.filter(answer=answer_string, question=q):
            return JsonResponse({"success": False, "error": "Answer already exists"})

        a = AnswerModel(question=q, answer=answer_string, status=status)
        a.save()
        return JsonResponse({"success": True, "error": "Answer added"})

    return JsonResponse({"success": False, "error": "Invalid data"})


@login_required
def answer_suggestion_confirm(request):
    if request.method == "POST":
        question_type = request.POST.get("question_type")
        aid = request.POST.get("aid")
        status = request.POST.get("status")

        if question_type == "anatomy":
            AnswerModel = AnatomyAnswer
            question = AnatomyQuestion
        elif question_type == "rapid":
            AnswerModel = RapidAnswer
            question = Rapid
        else:
            return JsonResponse({"success": False, "error": "Invalid question type"})

        answer = get_object_or_404(AnswerModel, pk=aid)
        answer.proposed = False
        answer.status = status
        answer.save()
        return JsonResponse({"success": True, "error": "Answer changed"})


@login_required
@user_passes_test(feedback_checker)
def view_feedback(request):
    # exam = get_object_or_404(Exam, pk=pk)

    feedback_notes = QuestionNote.objects.filter(complete=False)

    return render(
        request,
        "view_feedback.html",
        {
            "notes": feedback_notes,
        },
    )


def privacy_view(request):
    return render(request, "privacy.html")


def cimar(request):
    if "link_id" in request.GET:
        link_id = request.GET["link_id"]
        return render(request, "cimar.html", {"link_id": link_id})
    return render(request, "cimar.html")


def about_view(request):
    return render(request, "about.html")


def stats(request):
    collection_count = CaseCollection.objects.filter(archive=False).count()
    case_count = AtlasCase.objects.filter().count()

    anatomy_exam_count = AnatomyExam.objects.filter(archive=False).count()
    anatomy_question_count = AnatomyQuestion.objects.filter().count()
    return render(
        request,
        "stats.html",
        {
            "collection_count": collection_count,
            "case_count": case_count,
            "anatomy_exam_count": anatomy_exam_count,
            "anatomy_question_count": anatomy_question_count,
        },
    )


# HTTP Error 400
def page_not_found(request, exception):
    # Provide the exception message (reason) to the template so callers
    # can show a user-friendly explanation when available.
    context = {"reason": str(exception) if exception is not None else "Not found"}
    response = render(request, "404.html", context=context)

    response.status_code = 404

    return response


def page_forbidden(request, exception):
    response = render(request, "403.html", context={"exception": exception})

    response.status_code = 403

    return response


def server_error(request):
    response = render(request, "500.html")

    response.status_code = 500

    return response


class UserListView(CidManagerRequiredMixin, FilterView):
    model = User
    template_name = "user_list_view.html"

    filterset_class = UserListFilter


class UserListTableView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
    model = User
    table_class = UserUserTable
    template_name = "generic/user_view.html"

    filterset_class = UserUserFilter

    # table_pagination = {"per_page": 5}

    def get_paginate_by(self, table_data) -> int | None:
        return 100
        # return super().get_paginate_by(table_data)

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

        # filters = {"archive": False, "exam_mode": True}

        # physics_exams = [(i.name, i.pk) for i in PhysicsExam.objects.filter(**filters)]
        # rapid_exams = [(i.name, i.pk) for i in RapidsExam.objects.filter(**filters)]
        # sba_exams = [(i.name, i.pk) for i in SbasExam.objects.filter(**filters)]
        # longs_exams = [(i.name, i.pk) for i in LongsExam.objects.filter(**filters)]
        # anatomy_exams = [(i.name, i.pk) for i in AnatomyExam.objects.filter(**filters)]
        # casecollection_exams = [
        #    (i.name, i.pk)
        #    for i in CaseCollection.objects.filter(archive=False, collection_type__gt=0)
        # ]

        # context["physics_exams"] = physics_exams
        # context["rapid_exams"] = rapid_exams
        # context["sba_exams"] = sba_exams
        # context["longs_exams"] = longs_exams
        # context["anatomy_exams"] = anatomy_exams
        # context["casecollection_exams"] = casecollection_exams

        # cid_user_groups = [
        #    (i.name, i.pk) for i in CidUserGroup.objects.filter(archive=False)
        # ]

        # context["cid_user_groups"] = cid_user_groups
        return context


class DeleteUserView(CidManagerRequiredMixin, DeleteView):
    model = User
    template_name: str = "confirm_delete.html"
    success_url = reverse_lazy("accounts_list")


class UpdateUserView(CidManagerRequiredMixin, UpdateView):
    model = User
    fields = [
        "first_name",
        "last_name",
        "email",
        "is_active",
    ]  # Keep listing whatever fields
    # the combined UserProfile and User exposes.
    template_name = "user_update.html"
    slug_field = "username"
    slug_url_kwarg = "slug"

    def get_success_url(self):
        view_name = "account_profile"
        # No need for reverse_lazy here, because it's called inside the method
        return reverse(view_name, kwargs={"slug": self.object.username})


class UpdateUserProfileAdminView(CidManagerRequiredMixin, UpdateView):
    model = UserProfile
    fields = [
        "supervisor",
        "grade",
        "registration_number",
        "peninsula_trainee",
    ]  # Keep listing whatever fields
    template_name = "user_update_profile.html"
    slug_field = "user__username"
    slug_url_kwarg = "slug"

    def get_success_url(self):
        view_name = "account_profile"
        # No need for reverse_lazy here, because it's called inside the method
        return reverse(view_name, kwargs={"slug": self.object.username})


class UpdateUserProfileView(UpdateView):
    model = UserProfile
    fields = [
        "supervisor",
        "grade",
        "registration_number",
        # "peninsula_trainee",
    ]  # Keep listing whatever fields
    template_name = "user_update_profile.html"
    slug_field = "user__username"
    slug_url_kwarg = "slug"

    def dispatch(self, request, *args, **kwargs):
        if (
            request.user.is_superuser
            or request.user.groups.filter(name="cid_user_manager").exists()
        ):
            self.fields = [
                "supervisor",
                "grade",
                "registration_number",
                "site",
                "peninsula_trainee",
            ]
        # Check permissions for the request.user here
        elif request.user != self.get_object().user:
            raise Http404
        elif request.user.userprofile.peninsula_trainee:
            self.fields = ["supervisor", "grade", "registration_number", "site"]

        else:
            self.fields = ["grade", "registration_number", "site"]
        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs: reverse_lazy) -> dict[str, Any]:
        context = super().get_context_data(**kwargs)

        form = context["form"]

        if "supervisor" in form.fields:
            # form.fields["supervisor"].queryset = Supervisor.objects.all()
            form.fields["supervisor"] = forms.ModelChoiceField(
                Supervisor.objects.all().order_by("name"),
                required=False,
                widget=autocomplete.ModelSelect2(url="generic:supervisor-autocomplete"),
            )
        return context

    def get_success_url(self):
        if "redirect" in self.request.GET:
            return self.request.GET["redirect"]

        if (
            self.request.user.is_superuser
            or self.request.user.groups.filter(name="cid_user_manager").exists()
        ):
            view_name = "account_profile"
            # No need for reverse_lazy here, because it's called inside the method
            return reverse(view_name, kwargs={"slug": self.object.username})
        else:
            return reverse("profile")


# class UpdateUser(TemplateView):
#
#    user_form_class = UserForm
#    comment_form_class = UserProfileForm
#    template_name = 'user_update.html'
#    slug_field = 'username'
#    slug_url_kwarg = 'slug'
#
#    def post(self, request):
#        post_data = request.POST or None
#        user_form = self.user_form_class(post_data, prefix='user')
#        profile_form = self.profile_form_class(post_data, prefix='userprofile')
#
#        context = self.get_context_data(post_form=user_form,
#                                        profile_form=profile_form)
#
#        if user_form.is_valid():
#            self.form_save(user_form)
#        if profile_form.is_valid():
#            self.form_save(profile_form)
#
#        return self.render_to_response(context)
#
#    def form_save(self, form):
#        obj = form.save()
#        #messages.success(self.request, "{} saved successfully".format(obj))
#        return obj
#
#    def get(self, request, *args, **kwargs):
#        return self.post(request, *args, **kwargs)


@user_is_cid_user_manager
def accounts_check_users(request):
    if request.method == "POST":
        users = json.loads(request.POST.get("user_list"))

        existing_users = []

        for email in users:
            if User.objects.filter(username=email).exists():
                existing_users.append(email)

        return JsonResponse({"users": existing_users})

    return


@user_is_cid_user_manager
def accounts_bulk_create_check(request):
    if not request.method == "POST":
        return HttpResponse("Invalid request")

    user = {}

    user["email"] = request.POST.get("email")
    user["first_name"] = request.POST.get("first_name")
    user["last_name"] = request.POST.get("last_name")
    user["grade"] = request.POST.get("grade")
    user["supervisor_email"] = request.POST.get("supervisor_email")
    user["supervisor_name"] = request.POST.get("supervisor_name")

    errors = []
    info = []

    if user["email"] == "undefined":
        errors.append("No email provided")

    if User.objects.filter(username=user["email"]).exists():
        errors.append("User already exists")

    if not UserGrades.objects.filter(name=user["grade"]):
        errors.append("Invalid grade")

    # Treat empty, missing or the string 'undefined' as no supervisor email provided
    if not user.get("supervisor_email") or user["supervisor_email"] == "undefined":
        # Supervisor email not provided; try to match by name
        if not user.get("supervisor_name"):
            errors.append("No supervisor provided")
        else:
            supervisor = Supervisor.objects.filter(name__iexact=user["supervisor_name"])
            if not supervisor.exists():
                info.append("Supervisor does not exist (will be created)")
            else:
                info.append("Supervisor exists (matched by name)")
                if supervisor.count() > 1:
                    errors.append("More than one supervisor with that name")
                # no further name check needed since matched by name
    else:
        supervisor = Supervisor.objects.filter(email=user["supervisor_email"])

        if not supervisor:
            info.append("Supervisor does not exist (will be created)")
        else:
            info.append("Supervisor exists")

            if supervisor.count() > 1:
                errors.append("More than one supervisor with that email")

            # Compare names case-insensitively and trimmed to avoid false mismatches
            try:
                sup_name = (supervisor.first().name or '').strip().lower()
                posted_name = (user.get("supervisor_name") or '').strip().lower()
                if posted_name and sup_name != posted_name:
                    errors.append("Supervisor name does not match")
            except Exception:
                pass

    if errors:
        return HttpResponse(
            format_html("<span class='error'>{}</span>", "<br/>".join(errors))
        )

    return HttpResponse(format_html("<span class='info'>{}</span>", "<br/>".join(info)))


@user_is_cid_user_manager
def accounts_bulk_create(request):
    if request.method == "POST":
        context = {}

        users = json.loads(request.POST.get("usersjson"))

        created_users = []
        existing_users = []
        error_text = ""

        try:
            users = json.loads(request.POST.get("usersjson"))

            for user in users:
                try:
                    existing_user = User.objects.get(username=user["email"])
                    existing_users.append(user["email"])

                    user_profile = UserProfile.objects.get(user=existing_user)

                    # Update grade and supervisor details
                    # This code is dup[licated]
                    if "grade" in user:
                        if not user["grade"].startswith("ST"):
                            user["grade"] = f"ST{user['grade']}"

                        grade = UserGrades.objects.get(name=user["grade"])
                        user_profile.grade = grade

                    if "supervisor_name" in user and user.get("supervisor_name"):
                        sup_email = user.get("supervisor_email")
                        # If supervisor email provided, prefer that. Otherwise try to match by name or create placeholder
                        if sup_email and sup_email != 'undefined':
                            s, created = Supervisor.objects.get_or_create(email=sup_email, defaults={"name": user.get("supervisor_name")})
                        else:
                            # try match by name
                            s_qs = Supervisor.objects.filter(name__iexact=user.get("supervisor_name"))
                            if s_qs.exists():
                                s = s_qs.first()
                                created = False
                            else:
                                import uuid
                                placeholder = f"no-email-{uuid.uuid4().hex}@local.invalid"
                                s = Supervisor.objects.create(email=placeholder, name=user.get("supervisor_name"))
                                created = True

                        if created:
                            s.save()

                        user_profile.supervisor = s
                    user_profile.save()
                except User.DoesNotExist:
                    # Start by checking the email
                    try:
                        validate_email(user["email"])
                    except ValidationError:
                        error_text = (
                            error_text
                            + f"<p>{user['email']} does not appear to be a valid email (account for {user['first_name']} {user['last_name']} not created).</p>"
                        )
                        continue

                    # Try to create the user
                    try:
                        user_dict = {
                            "username": user["email"],
                            "first_name": user["first_name"],
                            "last_name": user["last_name"],
                            "email": user["email"],
                            "password": secrets.token_hex(nbytes=16),
                        }
                        new_user = User.objects.create_user(**user_dict)
                    except Exception as error:
                        error_text = (
                            error_text
                            + f"<p>Error creating user: {user['email']}<br/>{error}</p>"
                        )
                        # No need to try and create a profile if we can't create a user
                        continue

                    try:
                        user_profile = UserProfile.objects.get(user=new_user)
                        user_profile.peninsula_trainee = True
                        # TODO: check supervisor details are correct
                        if "supervisor_name" in user and user.get("supervisor_name"):
                            sup_email = user.get("supervisor_email")
                            if sup_email and sup_email != 'undefined':
                                s, created = Supervisor.objects.get_or_create(email=sup_email, defaults={"name": user.get("supervisor_name")})
                            else:
                                s_qs = Supervisor.objects.filter(name__iexact=user.get("supervisor_name"))
                                if s_qs.exists():
                                    s = s_qs.first()
                                    created = False
                                else:
                                    import uuid
                                    placeholder = f"no-email-{uuid.uuid4().hex}@local.invalid"
                                    s = Supervisor.objects.create(email=placeholder, name=user.get("supervisor_name"))
                                    created = True

                            if created:
                                s.save()

                            user_profile.supervisor = s

                        if "grade" in user:
                            if not user["grade"].startswith("ST"):
                                user["grade"] = f"ST{user['grade']}"

                            grade = UserGrades.objects.get(name=user["grade"])
                            user_profile.grade = grade

                        user_profile.save()

                        created_users.append(user["email"])
                    except Exception as error:
                        # If we fail to create the profile delete the user
                        new_user.delete()
                        error_text = (
                            error_text
                            + f"<p>Error creating user profile: {user['email']}<br/>{error} (user not created)</p>"
                        )

        except Exception as error:
            error_text = error_text + f"<p>Error creating users.<br/>{error}</p>"

        context["error"] = mark_safe(error_text)
        context["created_users"] = created_users

        if existing_users:
            context["message"] = "The following users already exist: " + ", ".join(
                existing_users
            )

        return render(request, "accounts_bulk_create.html", context)
    else:
        return render(request, "accounts_bulk_create.html", {})


def request_cid_details(request):
    email = request.POST.get("email")
    try:
        validate_email(email)
    except ValidationError:
        return HttpResponse("Invalid email.")

    cid_users = CidUser.objects.filter(email=email)

    for cid_user in cid_users:
        cid_user.email_details(resend=True)
        cid_user.email_details()

    return HttpResponse(
        f"Candidate details will be sent to the requested email (if it is found in the system). If you have not received an email after a few minutes you may need to contact <a href='mailto:{settings.CONTACT_EMAIL}'>{settings.CONTACT_EMAIL}</a>"
    )


def cimar_study_dicom_json(request, uuid="c9417b53-87aa-4c40-aca0-3fa34c225536"):
    with CimarAPI(request.user.userprofile.cimar_sid) as api:
        # studies = api.list_studies()
        # test_study = studies["studies"][0]

        # schema = api.get_study_schema(test_study)

        schema = api.get_study_schema_by_uuid("c9417b53-87aa-4c40-aca0-3fa34c225536")

        return JsonResponse(api.get_study_dicom_json(schema))


def cimar_study_viewer(request, uuid="c9417b53-87aa-4c40-aca0-3fa34c225536"):
    case = get_object_or_404(CimarCase, uuid=uuid)

    viewer_link = case.viewer_link + f"?sid={request.user.userprofile.cimar_sid}"
    return render(request, "cimar_embed.html", {"viewer_link": viewer_link})


def cimar_study_viewer_embed(request, uuid="c9417b53-87aa-4c40-aca0-3fa34c225536"):
    case = get_object_or_404(CimarCase, uuid=uuid)

    viewer_link = case.viewer_link + f"?sid={request.user.userprofile.cimar_sid}"
    return render(request, "cimar_embed.html#embed", {"viewer_link": viewer_link})


def cimar_study_series_block(request, uuid):
    case = get_object_or_404(CimarCase, uuid=uuid)

    return render(
        request,
        "cimar_series.html",
        {
            "series_data": case.series_data["series"],
            "cimar_sid": request.user.userprofile.cimar_sid,
        },
    )


def cimar_status(request):
    login_status = CimarAPI(request.user.userprofile.cimar_sid).check_login_status()

    if login_status:
        return HttpResponse("")
    else:
        return render(
            request,
            "cimar_login.html#login-block",
            {
                "login_status": login_status,
                "cimar_sid": request.user.userprofile.cimar_sid,
            },
        )


def cimar_logout(request):
    CimarAPI(request.user.userprofile.cimar_sid).logout()
    return HttpResponse("Logged out")


def cimar_login(request):
    if request.htmx:
        username = request.POST.get("username")
        password = request.POST.get("password")

        if not username and not password:
            return HttpResponse("Please fill in login details")

        try:
            sid = CimarAPI().login(username, password)

            request.user.userprofile.cimar_sid = sid
            request.user.userprofile.save()
        except InvalidCredentials:
            return HttpResponse("Invalid login details")

        return HttpResponse("Logged in")

    else:
        try:
            login_status = CimarAPI(
                request.user.userprofile.cimar_sid
            ).check_login_status()
        except NoSession:
            login_status = False
        return render(
            request,
            "cimar_login.html",
            {
                "login_status": login_status,
                "cimar_sid": request.user.userprofile.cimar_sid,
            },
        )
