from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db import IntegrityError, transaction
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone

from accounts.permissions import accountant_required
from contributions.models import Contribution
from core.project_utils import get_current_project, require_active_project
from members.models import Member


def _current_project(request):
    return get_current_project(request)


@login_required
def member_list(request):
    project = _current_project(request)
    members = Member.objects.filter(project_id=project.pk) if project else Member.objects.none()
    return render(request, "members/member_list.html", {"members": members, "project": project})


@login_required
def member_detail(request, pk):
    project = _current_project(request)
    member = get_object_or_404(Member, pk=pk, project_id=project.pk)
    contributions = member.contributions.all()
    return render(request, "members/member_detail.html", {
        "member": member, "contributions": contributions,
    })


def _clean_mobile(value):
    # Keep the stored value human-readable while preventing accidental duplicates
    # caused by leading/trailing whitespace.
    return " ".join((value or "").strip().split())


def _member_form_context(request, project, member=None, error=None):
    """Preserve submitted form values when validation fails."""
    data = {
        "full_name": request.POST.get("full_name", "") if request.method == "POST" else (member.full_name if member else ""),
        "mobile_number": request.POST.get("mobile_number", "") if request.method == "POST" else (member.mobile_number if member else ""),
        "email": request.POST.get("email", "") if request.method == "POST" else (member.email if member else ""),
        "nid_number": request.POST.get("nid_number", "") if request.method == "POST" else (member.nid_number if member else ""),
        "tin_number": request.POST.get("tin_number", "") if request.method == "POST" else (member.tin_number if member else ""),
        "present_address": request.POST.get("present_address", "") if request.method == "POST" else (member.present_address if member else ""),
        "permanent_address": request.POST.get("permanent_address", "") if request.method == "POST" else (member.permanent_address if member else ""),
        "share_percent": request.POST.get("share_percent", "0") if request.method == "POST" else (member.share_percent if member else "0"),
        "committed_amount": request.POST.get("committed_amount", "0") if request.method == "POST" else (member.committed_amount if member else "0"),
        "starting_balance": request.POST.get("starting_balance", "0") if request.method == "POST" else (member.starting_balance if member else "0"),
        "join_date": request.POST.get("join_date", "") if request.method == "POST" else (member.join_date if member else ""),
    }
    return {"project": project, "member": member, "form_data": data, "form_error": error}


@accountant_required
@require_active_project
def member_create(request):
    project = _current_project(request)
    if request.method == "POST":
        mobile = _clean_mobile(request.POST.get("mobile_number"))
        if not mobile:
            return render(request, "members/member_form.html",
                          _member_form_context(request, project, error="Mobile number is required."))

        # The database has a unique constraint on (project, mobile_number).
        # Check before INSERT so users get a normal form message instead of MySQL 1062.
        if Member.objects.filter(project_id=project.pk, mobile_number=mobile).exists():
            return render(
                request,
                "members/member_form.html",
                _member_form_context(
                    request,
                    project,
                    error=f"A member with mobile number {mobile} already exists in this company. Please use another mobile number or edit the existing member.",
                ),
            )

        try:
            with transaction.atomic():
                member = Member.objects.create(
                    project_id=project.pk,
                    full_name=request.POST.get("full_name", "").strip(),
                    mobile_number=mobile,
                    email=request.POST.get("email", "").strip(),
                    address=request.POST.get("present_address", "").strip(),
                    nid_number=request.POST.get("nid_number", "").strip(),
                    tin_number=request.POST.get("tin_number", "").strip(),
                    present_address=request.POST.get("present_address", "").strip(),
                    permanent_address=request.POST.get("permanent_address", "").strip(),
                    photo=request.FILES.get("photo"),
                    share_percent=request.POST.get("share_percent") or 0,
                    committed_amount=request.POST.get("committed_amount") or 0,
                    starting_balance=request.POST.get("starting_balance") or 0,
                    join_date=request.POST.get("join_date") or None,
                    created_by=request.user,
                )

                # Optional: record this member's first deposit in the same
                # step, instead of requiring a separate trip to Deposits.
                initial_amount = request.POST.get("initial_deposit_amount")
                if initial_amount and float(initial_amount) > 0:
                    Contribution.objects.create(
                        member=member,
                        date=request.POST.get("initial_deposit_date") or timezone.now().date(),
                        amount=initial_amount,
                        payment_method=request.POST.get("initial_deposit_method") or "cash",
                        reference_no=request.POST.get("initial_deposit_reference", "").strip(),
                        remarks="Recorded with member creation.",
                        created_by=request.user,
                    )
        except IntegrityError as exc:
            # Protect against two browser requests arriving at the same time.
            if "members_member_project_id_mobile_number" in str(exc) or "mobile_number" in str(exc):
                return render(
                    request,
                    "members/member_form.html",
                    _member_form_context(
                        request,
                        project,
                        error=f"A member with mobile number {mobile} already exists in this company. The duplicate member was not added.",
                    ),
                )
            raise

        messages.success(request, "Member added successfully.")
        return redirect("members:member_list")
    return render(request, "members/member_form.html", _member_form_context(request, project))


@accountant_required
@require_active_project
def member_edit(request, pk):
    project = _current_project(request)
    member = get_object_or_404(Member, pk=pk, project_id=project.pk)
    if request.method == "POST":
        mobile = _clean_mobile(request.POST.get("mobile_number"))
        if not mobile:
            return render(request, "members/member_form.html",
                          _member_form_context(request, project, member, "Mobile number is required."))

        # Exclude the member being edited. Keeping the same mobile number is valid.
        if Member.objects.filter(project_id=project.pk, mobile_number=mobile).exclude(pk=member.pk).exists():
            return render(
                request,
                "members/member_form.html",
                _member_form_context(
                    request,
                    project,
                    member,
                    f"Another member with mobile number {mobile} already exists in this company. Please use another number.",
                ),
            )

        member.full_name = request.POST.get("full_name", "").strip()
        member.mobile_number = mobile
        member.email = request.POST.get("email", "").strip()
        member.address = request.POST.get("present_address", "").strip()
        member.nid_number = request.POST.get("nid_number", "").strip()
        member.tin_number = request.POST.get("tin_number", "").strip()
        member.present_address = request.POST.get("present_address", "").strip()
        member.permanent_address = request.POST.get("permanent_address", "").strip()
        if request.FILES.get("photo"):
            member.photo = request.FILES.get("photo")
        member.share_percent = request.POST.get("share_percent") or 0
        member.committed_amount = request.POST.get("committed_amount") or 0
        member.starting_balance = request.POST.get("starting_balance") or 0
        member.join_date = request.POST.get("join_date") or None
        member.updated_by = request.user

        try:
            with transaction.atomic():
                member.save()
        except IntegrityError as exc:
            if "members_member_project_id_mobile_number" in str(exc) or "mobile_number" in str(exc):
                return render(
                    request,
                    "members/member_form.html",
                    _member_form_context(
                        request,
                        project,
                        member,
                        f"Another member with mobile number {mobile} already exists in this company. No changes were saved.",
                    ),
                )
            raise

        messages.success(request, "Member updated successfully.")
        return redirect("members:member_list")
    return render(request, "members/member_form.html", _member_form_context(request, project, member))
