import csv
import io
import os
from datetime import date
from decimal import Decimal

from django.contrib.auth.decorators import login_required
from django.db.models import Sum, Count, Max
from django.http import HttpResponse
from django.shortcuts import render

from contractors.models import Contractor, ContractorPayment
from contributions.models import Contribution
from core.project_utils import get_current_project
from expenses.models import Expense
from finance.models import BankAccount, CashTransaction
from labor.models import Worker, WorkerPayment
from materials.models import Material
from members.models import Member


def _current_project(request):
    return get_current_project(request)


def _csv_response(filename, header, rows):
    response = HttpResponse(content_type="text/csv; charset=utf-8")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    response.write("\ufeff")  # Excel-friendly UTF-8 BOM
    writer = csv.writer(response)
    writer.writerow(header)
    for row in rows:
        writer.writerow(row)
    return response


def _pdf_response(filename, title, headers, rows, summary=None):
    """Create a printable PDF report using ReportLab.

    A bundled DejaVu font is used so member names and other Unicode text render
    correctly on Windows/Linux servers. Currency is rendered as BDT in PDFs to
    avoid font-dependent currency-glyph problems while the web UI continues to
    show the Bangladeshi taka symbol.
    """
    try:
        from reportlab.lib import colors
        from reportlab.lib.enums import TA_CENTER, TA_RIGHT
        from reportlab.lib.pagesizes import A4, landscape
        from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
        from reportlab.lib.units import mm
        from reportlab.pdfbase import pdfmetrics
        from reportlab.pdfbase.ttfonts import TTFont
        from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
    except ImportError as exc:
        return HttpResponse(
            "PDF export requires reportlab. Run: pip install -r requirements.txt",
            status=500,
            content_type="text/plain",
        )

    font_regular = os.path.join(os.path.dirname(__file__), "..", "static", "fonts", "DejaVuSans.ttf")
    font_bold = os.path.join(os.path.dirname(__file__), "..", "static", "fonts", "DejaVuSans-Bold.ttf")
    try:
        if os.path.exists(font_regular):
            pdfmetrics.registerFont(TTFont("AppDejaVu", font_regular))
            if os.path.exists(font_bold):
                pdfmetrics.registerFont(TTFont("AppDejaVu-Bold", font_bold))
            body_font = "AppDejaVu"
            bold_font = "AppDejaVu-Bold" if os.path.exists(font_bold) else body_font
        else:
            body_font = "Helvetica"
            bold_font = "Helvetica-Bold"
    except Exception:
        body_font = "Helvetica"
        bold_font = "Helvetica-Bold"

    def clean(value):
        if value is None:
            return ""
        if isinstance(value, Decimal):
            return f"{value:,.2f}"
        if isinstance(value, float):
            return f"{value:,.2f}"
        return str(value)

    buffer = io.BytesIO()
    doc = SimpleDocTemplate(
        buffer,
        pagesize=landscape(A4),
        rightMargin=10 * mm,
        leftMargin=10 * mm,
        topMargin=12 * mm,
        bottomMargin=12 * mm,
        title=title,
        author="Construction Accounting",
    )
    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        "ReportTitle", parent=styles["Title"], fontName=bold_font,
        fontSize=16, leading=20, alignment=TA_CENTER, spaceAfter=8,
    )
    body_style = ParagraphStyle(
        "ReportBody", parent=styles["BodyText"], fontName=body_font,
        fontSize=8, leading=10,
    )
    header_style = ParagraphStyle(
        "ReportHeader", parent=body_style, fontName=bold_font,
        textColor=colors.white, alignment=TA_CENTER,
    )
    right_style = ParagraphStyle(
        "ReportRight", parent=body_style, alignment=TA_RIGHT,
    )

    story = [Paragraph(title, title_style)]
    if summary:
        for line in summary:
            story.append(Paragraph(str(line), body_style))
        story.append(Spacer(1, 5 * mm))

    table_data = [[Paragraph(clean(h), header_style) for h in headers]]
    for row in rows:
        cells = []
        for value in row:
            text = clean(value).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
            cells.append(Paragraph(text, body_style))
        table_data.append(cells)

    if not rows:
        table_data.append([Paragraph("No records found.", body_style)] + [""] * (max(1, len(headers)) - 1))

    col_count = max(1, len(headers))
    usable_width = landscape(A4)[0] - 20 * mm
    col_widths = [usable_width / col_count] * col_count
    table = Table(table_data, colWidths=col_widths, repeatRows=1, hAlign="LEFT")
    table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#343a40")),
        ("GRID", (0, 0), (-1, -1), 0.35, colors.HexColor("#adb5bd")),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("LEFTPADDING", (0, 0), (-1, -1), 4),
        ("RIGHTPADDING", (0, 0), (-1, -1), 4),
        ("TOPPADDING", (0, 0), (-1, -1), 4),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f8f9fa")]),
    ]))
    story.append(table)
    doc.build(story)
    buffer.seek(0)
    response = HttpResponse(buffer.getvalue(), content_type="application/pdf")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    return response


def _export_requested(request, filename, title, headers, rows, summary=None):
    fmt = request.GET.get("format", "").lower()
    if fmt == "csv":
        return _csv_response(filename.replace(".pdf", ".csv"), headers, rows)
    if fmt == "pdf":
        return _pdf_response(filename, title, headers, rows, summary=summary)
    return None


@login_required
def report_dashboard(request):
    project = _current_project(request)
    return render(request, "reports/report_dashboard.html", {"project": project})


@login_required
def deposit_expense_dashboard(request):
    """Combined deposit and expense dashboard with member-wise deposit details.

    The report is intentionally read-only. It keeps the existing Contribution
    model/table for database compatibility while presenting the business term
    "Deposit" to users.
    """
    project = _current_project(request)
    if not project:
        return render(request, "reports/deposit_expense_dashboard.html", {
            "project": None, "members": [], "deposits": [], "expenses": [],
            "total_deposits": Decimal("0"), "total_expense": Decimal("0"),
            "net": Decimal("0"), "start": "", "end": "", "selected_member": "",
        })

    start = request.GET.get("start", "").strip()
    end = request.GET.get("end", "").strip()
    selected_member = request.GET.get("member", "").strip()

    deposits_qs = Contribution.objects.filter(
        member__project_id=project.pk
    ).select_related("member").order_by("-date", "-created_at")
    expenses_qs = Expense.objects.filter(project_id=project.pk).select_related("category").order_by("-date", "-created_at")

    if start:
        deposits_qs = deposits_qs.filter(date__gte=start)
        expenses_qs = expenses_qs.filter(date__gte=start)
    if end:
        deposits_qs = deposits_qs.filter(date__lte=end)
        expenses_qs = expenses_qs.filter(date__lte=end)
    if selected_member:
        deposits_qs = deposits_qs.filter(member_id=selected_member)

    members_qs = Member.objects.filter(project_id=project.pk, is_active=True).order_by("full_name")
    from core.project_utils import get_membership
    membership = get_membership(request.user, project)
    if membership and membership.role == "member":
        members_qs = members_qs.filter(user=request.user)
        deposits_qs = deposits_qs.filter(member__user=request.user)

    total_deposits = deposits_qs.aggregate(t=Sum("amount"))["t"] or Decimal("0")
    total_expense = expenses_qs.aggregate(t=Sum("amount"))["t"] or Decimal("0")
    net = total_deposits - total_expense

    # Member-wise totals are calculated from the filtered deposit queryset,
    # so the selected date range and member filter are reflected consistently.
    member_rows = []
    for member in members_qs:
        member_deposits = deposits_qs.filter(member_id=member.pk)
        total = member_deposits.aggregate(t=Sum("amount"))["t"] or Decimal("0")
        latest = member_deposits.aggregate(d=Max("date"))["d"]
        member_rows.append({
            "member": member,
            "deposit_total": total,
            "latest_date": latest,
            "starting_balance": member.starting_balance,
            "current_balance": member.starting_balance + total,
            "due": max(member.starting_balance - total, Decimal("0")),
        })

    # Build a single export-friendly transaction dataset containing both
    # deposits and expenses, with dates, so users can reconcile the period.
    combined_rows = []
    for d in deposits_qs:
        combined_rows.append((d.date, d.member.full_name, d.amount, "", "Deposit", d.get_payment_method_display(), d.reference_no))
    for e in expenses_qs:
        combined_rows.append((e.date, "", "", e.amount, e.category.name if e.category else "Uncategorized", e.payment_method, e.paid_to))
    combined_rows.sort(key=lambda row: (row[0] or date.min, row[4] != "Deposit"), reverse=True)

    headers = ["Date", "Member", "Deposit", "Expense", "Category / Type", "Method", "Reference / Paid To"]
    summary = [
        f"Project: {project.name}",
        f"Period: {start or 'All'} to {end or 'All'}",
        f"Total Deposits: BDT {total_deposits:,.2f} | Total Expense: BDT {total_expense:,.2f} | Net: BDT {net:,.2f}",
    ]
    exported = _export_requested(request, "deposit_expense_dashboard.pdf", "Deposit & Expense Dashboard", headers, combined_rows, summary=summary)
    if exported:
        return exported

    return render(request, "reports/deposit_expense_dashboard.html", {
        "project": project, "members": members_qs, "member_rows": member_rows,
        "deposits": deposits_qs, "expenses": expenses_qs,
        "total_deposits": total_deposits, "total_expense": total_expense, "net": net,
        "start": start, "end": end, "selected_member": selected_member,
    })


@login_required
def member_ledger_report(request):
    project = _current_project(request)
    members = Member.objects.filter(project_id=project.pk) if project else Member.objects.none()
    from core.project_utils import get_membership
    membership = get_membership(request.user, project)
    if membership and membership.role == "member":
        members = members.filter(user=request.user)
    rows = [
        (
            m.full_name, m.mobile_number, m.share_percent, m.committed_amount,
            m.starting_balance, m.total_paid, m.current_balance,
            f"Surplus {m.surplus_amount}" if m.surplus_deficit > 0
            else (f"Deficit {m.due_amount}" if m.surplus_deficit < 0 else "Settled"),
        )
        for m in members
    ]
    headers = ["Member", "Mobile", "Share %", "Committed", "Starting Balance", "Deposits", "Current Balance", "Surplus / Deficit"]
    exported = _export_requested(request, "member_ledger.pdf", "Member-wise Ledger", headers, rows,
                                  [f"Project: {project.name}" if project else ""])
    if exported:
        return exported
    return render(request, "reports/member_ledger.html", {"members": members, "project": project})


@login_required
def member_due_report(request):
    project = _current_project(request)
    members_qs = Member.objects.filter(project_id=project.pk) if project else Member.objects.none()
    from core.project_utils import get_membership
    membership = get_membership(request.user, project)
    if membership and membership.role == "member":
        members_qs = members_qs.filter(user=request.user)
    members = [m for m in members_qs if m.due_amount > 0]
    rows = [(m.full_name, m.mobile_number, m.committed_amount, m.starting_balance, m.total_paid, m.due_amount) for m in members]
    headers = ["Member", "Mobile", "Committed Balance", "Starting Balance", "Deposits", "Due"]
    exported = _export_requested(request, "member_due.pdf", "Member Due Report", headers, rows)
    if exported:
        return exported
    return render(request, "reports/member_due.html", {"members": members, "project": project})


@login_required
def expense_report(request):
    project = _current_project(request)
    period = request.GET.get("period", "monthly")
    expenses = Expense.objects.filter(project_id=project.pk).select_related("category") if project else Expense.objects.none()
    start = request.GET.get("start")
    end = request.GET.get("end")
    if start:
        expenses = expenses.filter(date__gte=start)
    if end:
        expenses = expenses.filter(date__lte=end)
    elif period == "daily":
        expenses = expenses.filter(date=date.today())
    elif period == "monthly":
        today = date.today()
        expenses = expenses.filter(date__year=today.year, date__month=today.month)
    total = expenses.aggregate(t=Sum("amount"))["t"] or 0
    rows = [(e.date, e.category.name, e.amount, e.paid_to, e.payment_method) for e in expenses]
    headers = ["Date", "Category", "Amount", "Paid To", "Method"]
    exported = _export_requested(request, "expense_report.pdf", "Expense Report", headers, rows,
                                  [f"Period: {period}", f"Total Expense: BDT {total:,.2f}"])
    if exported:
        return exported
    return render(request, "reports/expense_report.html", {
        "expenses": expenses, "total": total, "project": project, "period": period,
        "start": start or "", "end": end or "",
    })


@login_required
def material_purchase_report(request):
    project = _current_project(request)
    materials = Material.objects.filter(project_id=project.pk) if project else Material.objects.none()
    purchases = []
    for m in materials:
        purchases += list(m.purchases.all())
    rows = [(p.date, p.material.name, p.quantity, p.material.unit, p.unit_price, p.total_cost, p.vendor) for p in purchases]
    headers = ["Date", "Material", "Qty", "Unit", "Unit Price", "Total", "Vendor"]
    exported = _export_requested(request, "material_purchase.pdf", "Material Purchase Report", headers, rows)
    if exported:
        return exported
    return render(request, "reports/material_purchase.html", {"purchases": purchases, "project": project})


@login_required
def material_stock_report(request):
    project = _current_project(request)
    materials = Material.objects.filter(project_id=project.pk) if project else Material.objects.none()
    rows = [(m.name, m.unit, m.total_purchased, m.total_used, m.remaining_stock, m.is_low_stock) for m in materials]
    headers = ["Material", "Unit", "Purchased", "Used", "Remaining", "Low Stock"]
    exported = _export_requested(request, "material_stock.pdf", "Material Stock Report", headers, rows)
    if exported:
        return exported
    return render(request, "reports/material_stock.html", {"materials": materials, "project": project})


@login_required
def contractor_payment_report(request):
    project = _current_project(request)
    contractors = Contractor.objects.filter(project_id=project.pk) if project else Contractor.objects.none()
    rows = [(c.name, c.work_order_no, c.contract_amount, c.total_billed, c.total_paid, c.remaining_amount) for c in contractors]
    headers = ["Contractor", "Work Order", "Contract Amount", "Billed", "Paid", "Remaining"]
    exported = _export_requested(request, "contractor_payment.pdf", "Contractor Payment Report", headers, rows)
    if exported:
        return exported
    return render(request, "reports/contractor_payment.html", {"contractors": contractors, "project": project})


@login_required
def worker_salary_report(request):
    project = _current_project(request)
    workers = Worker.objects.filter(project_id=project.pk) if project else Worker.objects.none()
    rows = [(w.name, w.work_type, w.total_days_worked, w.total_earned, w.total_paid, w.due_amount) for w in workers]
    headers = ["Worker", "Work Type", "Days Worked", "Earned", "Paid", "Due"]
    exported = _export_requested(request, "worker_salary.pdf", "Worker Salary Report", headers, rows)
    if exported:
        return exported
    return render(request, "reports/worker_salary.html", {"workers": workers, "project": project})


@login_required
def cash_book_report(request):
    project = _current_project(request)
    txns = CashTransaction.objects.filter(project_id=project.pk) if project else CashTransaction.objects.none()
    rows = [(t.date, t.get_txn_type_display(), t.amount, t.source_or_purpose, t.remarks) for t in txns]
    headers = ["Date", "Type", "Amount", "Source/Purpose", "Remarks"]
    exported = _export_requested(request, "cash_book.pdf", "Cash Book", headers, rows)
    if exported:
        return exported
    return render(request, "reports/cash_book_report.html", {"transactions": txns, "project": project})


@login_required
def bank_statement_report(request):
    project = _current_project(request)
    accounts = BankAccount.objects.filter(project_id=project.pk) if project else BankAccount.objects.none()
    rows = []
    for acc in accounts:
        for t in acc.transactions.all():
            rows.append((t.date, acc.bank_name, t.get_txn_type_display(), t.amount, t.remarks))
    headers = ["Date", "Bank", "Type", "Amount", "Remarks"]
    exported = _export_requested(request, "bank_statement.pdf", "Bank Statement", headers, rows)
    if exported:
        return exported
    return render(request, "reports/bank_statement_report.html", {"accounts": accounts, "project": project})


@login_required
def per_head_balance_report(request):
    project = _current_project(request)
    if not project:
        context = {
            "project": None, "member_count": 0, "total_member_balance": 0,
            "total_expense": 0, "per_head_balance": 0, "per_head_expense": 0,
            "per_head_amount_balance": 0, "position": "Balanced (0)", "member_rows": [],
        }
        if request.GET.get("format") == "pdf":
            return _pdf_response("per_head_balance.pdf", "Per-Head Expense & Balance Report",
                                 ["Project", "Active Members", "Total Balance", "Total Expense", "Per Head Balance", "Per Head Expense", "Net", "Position"],
                                 [], summary=["No active project selected."])
        return render(request, "reports/per_head_balance.html", context)

    members_qs = Member.objects.filter(project_id=project.pk, is_active=True).order_by("full_name")
    member_count = members_qs.count()
    total_member_balance = members_qs.aggregate(t=Sum("starting_balance"))["t"] or 0
    total_member_balance += Contribution.objects.filter(member__project_id=project.pk, member__is_active=True).aggregate(t=Sum("amount"))["t"] or 0
    total_expense = Expense.objects.filter(project_id=project.pk).aggregate(t=Sum("amount"))["t"] or 0
    per_head_balance = (total_member_balance / member_count) if member_count else 0
    per_head_expense = (total_expense / member_count) if member_count else 0
    per_head_amount_balance = per_head_balance - per_head_expense
    position = "Surplus (+)" if per_head_amount_balance > 0 else "Deficit (-)" if per_head_amount_balance < 0 else "Balanced (0)"

    member_rows = []
    for member in members_qs:
        paid = member.total_paid
        member_balance = member.current_balance
        net = member_balance - per_head_expense
        member_position = "Surplus (+)" if net > 0 else "Deficit (-)" if net < 0 else "Balanced (0)"
        member_rows.append({
            "member": member, "starting_balance": member.starting_balance, "paid": paid,
            "balance": member_balance, "allocated_expense": per_head_expense,
            "net_position": net, "position": member_position,
        })

    summary_headers = ["Project", "Active Members", "Total Member Balance", "Total Expense", "Per Head Balance", "Per Head Expense", "Per Head Amount Balance", "Position"]
    summary_rows = [(project.name, member_count, total_member_balance, total_expense, per_head_balance, per_head_expense, per_head_amount_balance, position)]
    fmt = request.GET.get("format", "").lower()
    if fmt == "csv":
        response = _csv_response("per_head_balance.csv", summary_headers, summary_rows)
        writer = csv.writer(response)
        writer.writerow([])
        writer.writerow(["Member", "Mobile", "Starting Balance", "Deposits", "Current Balance", "Per Head Expense", "Member Net Position", "Position"])
        for row in member_rows:
            writer.writerow([row["member"].full_name, row["member"].mobile_number, row["starting_balance"], row["paid"], row["balance"], row["allocated_expense"], row["net_position"], row["position"]])
        return response
    if fmt == "pdf":
        detail_headers = ["Member", "Mobile", "Starting Balance", "Deposits", "Current Balance", "Per Head Expense", "Net Position", "Position"]
        detail_rows = [(r["member"].full_name, r["member"].mobile_number, r["starting_balance"], r["paid"], r["balance"], r["allocated_expense"], r["net_position"], r["position"]) for r in member_rows]
        summary = [
            f"Project: {project.name}",
            f"Active Members: {member_count} | Total Member Balance: BDT {total_member_balance:,.2f} | Total Expense: BDT {total_expense:,.2f}",
            f"Per-Head Balance: BDT {per_head_balance:,.2f} | Per-Head Expense: BDT {per_head_expense:,.2f} | Net: BDT {per_head_amount_balance:,.2f} | {position}",
        ]
        return _pdf_response("per_head_balance.pdf", "Per-Head Expense & Balance Report", detail_headers, detail_rows, summary=summary)

    return render(request, "reports/per_head_balance.html", {
        "project": project, "member_count": member_count, "total_member_balance": total_member_balance,
        "total_expense": total_expense, "per_head_balance": per_head_balance,
        "per_head_expense": per_head_expense, "per_head_amount_balance": per_head_amount_balance,
        "position": position, "member_rows": member_rows,
    })


@login_required
def project_cost_report(request):
    project = _current_project(request)
    total_expense = (Expense.objects.filter(project_id=project.pk).aggregate(t=Sum("amount"))["t"] or 0) if project else 0
    total_contractor_paid = sum((c.total_paid for c in Contractor.objects.filter(project_id=project.pk)), 0) if project else 0
    total_worker_paid = sum((w.total_paid for w in Worker.objects.filter(project_id=project.pk)), 0) if project else 0
    total_material_purchase = sum((m.total_purchase_value for m in Material.objects.filter(project_id=project.pk)), 0) if project else 0
    total_cost = total_expense
    headers = ["Project", "Direct Expense", "Contractor Paid", "Worker Paid", "Material Purchase", "Total Cost"]
    rows = [(project.name if project else "", total_expense, total_contractor_paid, total_worker_paid, total_material_purchase, total_cost)]
    exported = _export_requested(request, "project_cost.pdf", "Project Cost Report", headers, rows)
    if exported:
        return exported
    return render(request, "reports/project_cost.html", {
        "project": project, "total_expense": total_expense, "total_contractor_paid": total_contractor_paid,
        "total_worker_paid": total_worker_paid, "total_material_purchase": total_material_purchase, "total_cost": total_cost,
    })


@login_required
def project_balance_report(request):
    project = _current_project(request)
    total_contribution = (Contribution.objects.filter(member__project_id=project.pk).aggregate(t=Sum("amount"))["t"] or 0) if project else 0
    total_expense = (Expense.objects.filter(project_id=project.pk).aggregate(t=Sum("amount"))["t"] or 0) if project else 0
    balance = total_contribution - total_expense

    expense_by_category = list(
        Expense.objects.filter(project_id=project.pk)
        .values("category__name")
        .annotate(total=Sum("amount"))
        .order_by("-total")
    ) if project else []
    for row in expense_by_category:
        row["percent"] = (row["total"] / total_expense * 100) if total_expense else 0

    if request.GET.get("format") in ("csv", "pdf"):
        headers = ["Category", "Amount", "% of Total Expense"]
        rows = [(row["category__name"] or "Uncategorized", row["total"], f"{row['percent']:.1f}%") for row in expense_by_category]
        rows.append(("", "", ""))
        rows.append(("TOTAL DEPOSITS", total_contribution, ""))
        rows.append(("TOTAL EXPENSES", total_expense, ""))
        rows.append(("ALL PROJECT BALANCE", balance, ""))
        exported = _export_requested(request, "project_balance.pdf", "All Project Balance & Expense Summary", headers, rows)
        if exported:
            return exported

    return render(request, "reports/project_balance.html", {
        "project": project, "total_contribution": total_contribution,
        "total_expense": total_expense, "balance": balance,
        "expense_by_category": expense_by_category,
    })
