import csv
import os
from datetime import date
from decimal import Decimal
from io import BytesIO
from pathlib import Path

from django.http import HttpResponse


def money(value):
    value = Decimal(value or 0)
    return f"{value:,.2f}"


def parse_iso_date(value, fallback):
    if not value:
        return fallback
    try:
        return date.fromisoformat(value)
    except (TypeError, ValueError):
        return fallback


def _csv_safe(value):
    """Prevent spreadsheet formula execution for user-controlled text cells."""
    if isinstance(value, str) and value and value[0] in ("=", "+", "-", "@", "\t", "\r"):
        return "'" + value
    return value


def csv_response(filename, headers, rows):
    response = HttpResponse(content_type="text/csv; charset=utf-8")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    # UTF-8 BOM keeps Bangla/Unicode readable when opened directly in Excel.
    response.write("\ufeff")
    writer = csv.writer(response)
    writer.writerow([_csv_safe(v) for v in headers])
    writer.writerows([[_csv_safe(v) for v in row] for row in rows])
    return response


def payroll_calculation_summary(record):
    """Compact explanation sourced only from the frozen payroll calculation snapshot."""
    snap = record.calculation_snapshot or {}
    segments = snap.get("salary_segments") or []
    attendance = snap.get("attendance") or []
    overtime = snap.get("overtime") or []
    service_days = 0
    for seg in segments:
        try:
            start = date.fromisoformat(str(seg.get("from")))
            end = date.fromisoformat(str(seg.get("to")))
            service_days += max(0, (end - start).days + 1)
        except (TypeError, ValueError):
            pass
    deduction_days = sum((Decimal(str(row.get("fraction") or 0)) for row in attendance), Decimal("0"))
    payable_days = max(Decimal("0"), Decimal(service_days) - deduction_days)
    ot_minutes = sum(int(row.get("minutes") or 0) for row in overtime)
    return {
        "service_days": service_days,
        "deduction_days": deduction_days,
        "payable_days": payable_days,
        "ot_minutes": ot_minutes,
        "salary_segments": segments,
        "policy": snap.get("policy") or {},
    }


def payroll_display_lines(record):
    items = list(record.line_items.all().order_by("kind", "pk"))
    if items:
        return [(x.component_name, x.kind, x.amount) for x in items]
    rows = [("Gross Salary", "EARNING", record.gross_salary)]
    if record.overtime_amount:
        rows.append(("Overtime", "EARNING", record.overtime_amount))
    if record.bonus_amount:
        rows.append(("Bonus", "EARNING", record.bonus_amount))
    if record.other_earnings:
        rows.append(("Other Earnings", "EARNING", record.other_earnings))
    if record.attendance_deduction:
        rows.append(("Attendance Deduction", "DEDUCTION", record.attendance_deduction))
    if record.deduction_amount:
        rows.append(("Other Deductions", "DEDUCTION", record.deduction_amount))
    if record.loan_recovery:
        rows.append(("Loan / Advance Recovery", "DEDUCTION", record.loan_recovery))
    return rows


def _payslip_unicode_font_paths():
    """Resolve a Bangla-capable font without bundling proprietary/system fonts.

    HRPAY_BANGLA_FONT / HRPAY_BANGLA_FONT_BOLD may be set explicitly.
    Windows normally provides Nirmala UI; Linux UAT images commonly provide
    Noto Sans Bengali. Final deployment should document the chosen font.
    """
    regular_candidates = [
        os.environ.get("HRPAY_BANGLA_FONT", ""),
        r"C:\Windows\Fonts\Nirmala.ttf",
        "/usr/share/fonts/truetype/noto/NotoSansBengali-Regular.ttf",
        "/usr/share/fonts/truetype/lohit-bengali/Lohit-Bengali.ttf",
    ]
    bold_candidates = [
        os.environ.get("HRPAY_BANGLA_FONT_BOLD", ""),
        r"C:\Windows\Fonts\NirmalaB.ttf",
        "/usr/share/fonts/truetype/noto/NotoSansBengali-Bold.ttf",
    ]

    def first_existing(items):
        for value in items:
            if value and Path(value).is_file():
                return value
        return ""

    regular = first_existing(regular_candidates)
    bold = first_existing(bold_candidates) or regular
    return regular, bold


def _register_payslip_unicode_fonts():
    try:
        from reportlab.pdfbase import pdfmetrics
        from reportlab.pdfbase.ttfonts import TTFont
    except ImportError as exc:
        raise RuntimeError("ReportLab is required for PDF payslip export.") from exc

    regular_path, bold_path = _payslip_unicode_font_paths()
    if not regular_path:
        raise RuntimeError(
            "Bangla-capable PDF font not found. Install Nirmala UI/Noto Sans Bengali "
            "or set HRPAY_BANGLA_FONT before generating payslips."
        )
    regular_name = "HRPayUnicode"
    bold_name = "HRPayUnicodeBold"
    registered = set(pdfmetrics.getRegisteredFontNames())
    if regular_name not in registered:
        pdfmetrics.registerFont(TTFont(regular_name, regular_path, shapable=True))
    if bold_name not in registered:
        pdfmetrics.registerFont(TTFont(bold_name, bold_path, shapable=True))
    return regular_name, bold_name


def payslip_pdf_response(record):
    """Generate a Unicode/Bangla-safe payslip with ReportLab shaping enabled."""
    try:
        from reportlab.lib.pagesizes import A4
        from reportlab.pdfgen import canvas
    except ImportError as exc:
        raise RuntimeError("ReportLab is required for PDF payslip export.") from exc

    regular_font, bold_font = _register_payslip_unicode_fonts()
    buffer = BytesIO()
    pdf = canvas.Canvas(buffer, pagesize=A4)
    width, height = A4
    left = 48
    y = height - 48

    company = record.period.company
    employee = record.employee
    period_label = f"{record.period.year}-{record.period.month:02d}"

    def draw(x, yy, value, *, bold=False, size=9):
        pdf.setFont(bold_font if bold else regular_font, size)
        pdf.drawString(x, yy, str(value), shaping=True)

    def draw_right(x, yy, value, *, bold=False, size=9):
        pdf.setFont(bold_font if bold else regular_font, size)
        pdf.drawRightString(x, yy, str(value), shaping=True)

    pdf.setTitle(f"Payslip {employee.employee_code} {period_label}")
    draw(left, y, company.name, bold=True, size=15)
    y -= 18
    for line in (company.address or "").splitlines()[:2]:
        draw(left, y, line[:95], size=9)
        y -= 12
    y -= 7
    draw(left, y, f"PAYSLIP — {period_label}", bold=True, size=12)
    y -= 22

    details = [
        ("Employee ID", employee.employee_code),
        ("Employee", employee.full_name),
        ("Department", str(employee.department)),
        ("Designation", str(employee.designation)),
        ("Payroll Status", record.period.status),
    ]
    for label, value in details:
        draw(left, y, f"{label}:", bold=True)
        draw(left + 100, y, str(value)[:70])
        y -= 14

    y -= 8
    draw(left, y, "Component", bold=True)
    draw(left + 250, y, "Type", bold=True)
    draw_right(width - left, y, "Amount (BDT)", bold=True)
    y -= 10
    pdf.line(left, y, width-left, y)
    y -= 14

    for name, kind, amount in payroll_display_lines(record):
        if y < 95:
            pdf.showPage()
            y = height - 55
        draw(left, y, str(name)[:45])
        draw(left + 250, y, "Earning" if kind == "EARNING" else "Deduction")
        draw_right(width-left, y, money(amount))
        y -= 14

    y -= 4
    pdf.line(left, y, width-left, y)
    y -= 20
    draw(left, y, "Net Payable", bold=True, size=11)
    draw_right(width-left, y, f"BDT {money(record.net_payable)}", bold=True, size=11)
    y -= 24
    summary = payroll_calculation_summary(record)
    draw(left, y, "Calculation Summary", bold=True, size=9)
    y -= 14
    draw(left, y, f"Service days: {summary['service_days']}  |  Payable days: {summary['payable_days']}  |  Deduction days: {summary['deduction_days']}", size=8)
    y -= 12
    draw(left, y, f"Approved OT: {summary['ot_minutes']} minutes", size=8)
    y -= 20
    draw(left, y, "System generated payslip. No signature is required unless company policy requires one.", size=8)

    pdf.save()
    response = HttpResponse(buffer.getvalue(), content_type="application/pdf")
    response["Content-Disposition"] = f'attachment; filename="payslip-{employee.employee_code}-{period_label}.pdf"'
    return response
