"""
ARQ Background Worker for INDELIS.

Replaces Celery+RabbitMQ with ARQ (async Redis queue).
ARQ is fully async and uses Redis — no separate message broker needed.

Run worker:
    arq src.worker.arq_app.WorkerSettings
"""
import logging
from typing import Optional
from uuid import UUID

import arq
from arq.connections import RedisSettings

from src.core.config import settings
from src.database.session import AsyncSessionLocal

logger = logging.getLogger(__name__)


# ── Task Definitions ──────────────────────────────────────────────────────────

async def send_welcome_email(ctx: dict, tenant_id: str, admin_email: str, subdomain: str):
    """Send welcome email after tenant signup."""
    logger.info(f"[email] Sending welcome email to {admin_email} for tenant {subdomain}")
    # TODO: Integrate AWS SES
    # ses_client = boto3.client("ses", region_name=settings.AWS_REGION)
    # ses_client.send_email(...)
    return {"status": "sent", "email": admin_email}


async def send_invoice_reminder(ctx: dict, invoice_id: str, tenant_id: str, days_overdue: int):
    """Send overdue invoice reminder email."""
    logger.info(f"[email] Sending {days_overdue}d reminder for invoice {invoice_id}")
    # TODO: Integrate SES + email template
    return {"status": "sent", "invoice_id": invoice_id}


async def generate_qr_code(ctx: dict, qr_code_id: str):
    """Fetch qr_codes row → SVG → S3 → PDF → S3 → update row."""
    from io import BytesIO
    import asyncio
    import qrcode
    import qrcode.image.svg
    import boto3
    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.utils import ImageReader
    from sqlalchemy import select
    from src.database.session import AsyncSessionLocal
    from src.apps.settings.models.qr_code import QRCode
    from src.core.config import settings

    async with AsyncSessionLocal() as db:
        result = await db.execute(select(QRCode).where(QRCode.id == UUID(qr_code_id)))
        qr = result.scalar_one_or_none()
        if not qr:
            logger.error(f"[qr] QRCode row {qr_code_id} not found")
            return {"status": "not_found", "qr_code_id": qr_code_id}

        content_url = qr.content_url
        tenant_id = str(qr.tenant_id)
        qr_type = qr.qr_type
        reference_id = qr.reference_id

    # Generate SVG
    svg_factory = qrcode.image.svg.SvgImage
    qr_obj = qrcode.QRCode(version=1, box_size=10, border=4)
    qr_obj.add_data(content_url)
    qr_obj.make(fit=True)
    svg_img = qr_obj.make_image(image_factory=svg_factory)
    svg_buf = BytesIO()
    svg_img.save(svg_buf)
    svg_bytes = svg_buf.getvalue()

    # Generate PNG for PDF embed
    png_buf = BytesIO()
    qr_png = qrcode.QRCode(version=1, box_size=10, border=4)
    qr_png.add_data(content_url)
    qr_png.make(fit=True)
    png_img = qr_png.make_image(fill_color="black", back_color="white")
    png_img.save(png_buf, format="PNG")
    png_bytes = png_buf.getvalue()

    # Generate PDF label
    pdf_buf = BytesIO()
    c = canvas.Canvas(pdf_buf, pagesize=A4)
    width, height = A4
    # Center QR
    qr_size = 200
    x = (width - qr_size) / 2
    y = (height - qr_size) / 2
    png_reader = ImageReader(BytesIO(png_bytes))
    c.drawImage(png_reader, x, y, width=qr_size, height=qr_size)
    # Add URL text below
    c.setFont("Helvetica", 9)
    c.drawCentredString(width / 2, y - 20, content_url[:80])
    c.save()
    pdf_bytes = pdf_buf.getvalue()

    # Upload to S3
    svg_key = f"tenant/{tenant_id}/qr/{qr_type}/{reference_id}.svg"
    pdf_key = f"tenant/{tenant_id}/qr/{qr_type}/{reference_id}.pdf"

    s3 = boto3.client(
        "s3",
        region_name=settings.AWS_REGION,
        aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
        aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
    )
    await asyncio.to_thread(
        s3.put_object,
        Bucket=settings.S3_BUCKET,
        Key=svg_key,
        Body=svg_bytes,
        ContentType="image/svg+xml",
    )
    await asyncio.to_thread(
        s3.put_object,
        Bucket=settings.S3_BUCKET,
        Key=pdf_key,
        Body=pdf_bytes,
        ContentType="application/pdf",
    )

    # Update DB row
    from datetime import datetime, timezone
    async with AsyncSessionLocal() as db:
        result = await db.execute(select(QRCode).where(QRCode.id == UUID(qr_code_id)))
        qr = result.scalar_one_or_none()
        if qr:
            qr.svg_s3_key = svg_key
            qr.pdf_s3_key = pdf_key
            qr.generated_at = datetime.now(timezone.utc)
            await db.commit()

    logger.info(f"[qr] QR code generated: {qr_code_id}")
    return {"status": "generated", "qr_code_id": qr_code_id}


async def regenerate_all_qr(ctx: dict, tenant_id: str, qr_type: Optional[str] = None):
    """Fan-out: enqueue generate_qr_code per active qr_codes row."""
    from sqlalchemy import select
    from src.database.session import AsyncSessionLocal
    from src.apps.settings.models.qr_code import QRCode

    tenant_uuid = UUID(tenant_id)
    async with AsyncSessionLocal() as db:
        filters = [QRCode.tenant_id == tenant_uuid, QRCode.is_active.is_(True)]
        if qr_type:
            filters.append(QRCode.qr_type == qr_type)
        result = await db.execute(select(QRCode.id).where(*filters))
        ids = [str(row[0]) for row in result.all()]

    redis = ctx["redis"]
    for qr_id in ids:
        await redis.enqueue_job("generate_qr_code", qr_id)

    logger.info(f"[qr] Enqueued {len(ids)} generate_qr_code jobs for tenant {tenant_id}")
    return {"enqueued": len(ids)}


async def generate_contract_pdf(ctx: dict, tenant_id: str, contract_id: str):
    """Generate signed contract PDF, upload to S3, and email the purchaser."""
    import asyncio
    import base64
    from decimal import Decimal
    from io import BytesIO
    from uuid import UUID

    import boto3
    from botocore.exceptions import NoCredentialsError, ClientError
    from reportlab.lib import colors
    from reportlab.lib.pagesizes import letter
    from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
    from reportlab.lib.units import inch
    from reportlab.lib.utils import ImageReader
    from reportlab.platypus import (
        SimpleDocTemplate,
        Paragraph,
        Spacer,
        Table,
        TableStyle,
        HRFlowable,
    )
    from sqlalchemy import select
    from sqlalchemy.orm import selectinload

    from src.apps.sales.models.contract import Contract
    from src.apps.sales.models.contract_line_item import ContractLineItem  # noqa: F401

    logger.info("[pdf] Generating contract PDF for contract %s", contract_id)

    # ── 1. Load contract with line items ─────────────────────────────────────
    tenant_uuid = UUID(tenant_id)
    contract_uuid = UUID(contract_id)

    async with AsyncSessionLocal() as db:
        result = await db.execute(
            select(Contract)
            .options(selectinload(Contract.line_items))
            .where(
                Contract.id == contract_uuid,
                Contract.tenant_id == tenant_uuid,
                Contract.deleted_at.is_(None),
            )
        )
        contract = result.scalar_one_or_none()

    if not contract:
        logger.error("[pdf] Contract %s not found for tenant %s", contract_id, tenant_id)
        return {"status": "not_found", "contract_id": contract_id}

    # ── 2. Build PDF with ReportLab ──────────────────────────────────────────
    buf = BytesIO()
    doc = SimpleDocTemplate(
        buf,
        pagesize=letter,
        rightMargin=0.75 * inch,
        leftMargin=0.75 * inch,
        topMargin=0.75 * inch,
        bottomMargin=0.75 * inch,
    )

    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        "ContractTitle",
        parent=styles["Title"],
        fontSize=16,
        spaceAfter=4,
        textColor=colors.HexColor("#1a1a2e"),
    )
    heading_style = ParagraphStyle(
        "SectionHeading",
        parent=styles["Heading2"],
        fontSize=11,
        spaceBefore=12,
        spaceAfter=4,
        textColor=colors.HexColor("#2563eb"),
    )
    normal = styles["Normal"]
    small_style = ParagraphStyle(
        "Small",
        parent=normal,
        fontSize=8,
        textColor=colors.HexColor("#6b7280"),
    )

    story = []

    # Letterhead
    story.append(Paragraph("INDELIS Cemetery", title_style))
    story.append(Paragraph("Cemetery Plot Purchase Agreement", styles["Heading1"]))
    story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor("#2563eb")))
    story.append(Spacer(1, 0.15 * inch))

    # Contract meta
    signed_date = (
        contract.signed_at.strftime("%B %d, %Y") if contract.signed_at else "N/A"
    )
    meta_data = [
        ["Contract Number:", contract.contract_number or "—"],
        ["Contract Type:", (contract.contract_type or "—").replace("_", " ").title()],
        ["Date Signed:", signed_date],
        ["Status:", (contract.status or "—").upper()],
    ]
    meta_table = Table(meta_data, colWidths=[2 * inch, 4 * inch])
    meta_table.setStyle(
        TableStyle([
            ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, -1), 10),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
            ("TOPPADDING", (0, 0), (-1, -1), 4),
            ("TEXTCOLOR", (0, 0), (0, -1), colors.HexColor("#374151")),
        ])
    )
    story.append(meta_table)
    story.append(Spacer(1, 0.2 * inch))

    # Purchaser details
    story.append(Paragraph("Purchaser Information", heading_style))
    purchaser_data = [
        ["Name:", contract.purchaser_name or "—"],
        ["Email:", contract.purchaser_email or "—"],
        ["Phone:", contract.purchaser_phone or "—"],
        ["Address:", contract.purchaser_address or "—"],
    ]
    purchaser_table = Table(purchaser_data, colWidths=[1.5 * inch, 5 * inch])
    purchaser_table.setStyle(
        TableStyle([
            ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, -1), 10),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
            ("TOPPADDING", (0, 0), (-1, -1), 4),
        ])
    )
    story.append(purchaser_table)
    story.append(Spacer(1, 0.2 * inch))

    # Line items table
    story.append(Paragraph("Items", heading_style))
    line_items = contract.line_items or []
    li_headers = [["Description", "Qty", "Unit Price", "Subtotal"]]
    li_rows = []
    subtotal = Decimal("0")
    for item in line_items:
        unit = Decimal(str(item.unit_price))
        qty = item.quantity
        total = Decimal(str(item.line_total))
        subtotal += total
        li_rows.append([
            item.description,
            str(qty),
            f"${unit:,.2f}",
            f"${total:,.2f}",
        ])

    if not li_rows:
        li_rows = [["No line items", "", "", ""]]

    hst_rate = Decimal("0.13")
    hst_amount = (subtotal * hst_rate).quantize(Decimal("0.01"))
    grand_total = subtotal + hst_amount

    summary_rows = [
        ["", "", "Subtotal:", f"${subtotal:,.2f}"],
        ["", "", "HST (13%):", f"${hst_amount:,.2f}"],
        ["", "", "Total:", f"${grand_total:,.2f}"],
    ]

    li_col_widths = [3.5 * inch, 0.5 * inch, 1.25 * inch, 1.25 * inch]
    li_table_data = li_headers + li_rows + summary_rows
    li_table = Table(li_table_data, colWidths=li_col_widths)
    li_table.setStyle(
        TableStyle([
            # Header row
            ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1e3a5f")),
            ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
            ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, 0), 10),
            ("ALIGN", (1, 0), (-1, 0), "RIGHT"),
            # Data rows
            ("FONTSIZE", (0, 1), (-1, -1), 9),
            ("ROWBACKGROUNDS", (0, 1), (-1, len(li_rows)), [colors.white, colors.HexColor("#f9fafb")]),
            ("ALIGN", (1, 1), (-1, -1), "RIGHT"),
            # Summary rows
            ("FONTNAME", (2, len(li_rows) + 1), (2, -1), "Helvetica-Bold"),
            ("LINEABOVE", (2, len(li_rows) + 1), (-1, len(li_rows) + 1), 1, colors.HexColor("#d1d5db")),
            # Grand total bold
            ("FONTNAME", (2, -1), (-1, -1), "Helvetica-Bold"),
            ("FONTSIZE", (2, -1), (-1, -1), 10),
            # Grid
            ("GRID", (0, 0), (-1, len(li_rows)), 0.5, colors.HexColor("#e5e7eb")),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
            ("TOPPADDING", (0, 0), (-1, -1), 6),
            ("LEFTPADDING", (0, 0), (-1, -1), 6),
            ("RIGHTPADDING", (0, 0), (-1, -1), 6),
        ])
    )
    story.append(li_table)
    story.append(Spacer(1, 0.15 * inch))

    # Payment plan
    if contract.payment_plan_type:
        story.append(
            Paragraph(
                f"<b>Payment Plan:</b> {contract.payment_plan_type.replace('_', ' ').title()}",
                normal,
            )
        )
        story.append(Spacer(1, 0.2 * inch))

    # Signature section
    story.append(Paragraph("Signatures", heading_style))
    story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#d1d5db")))
    story.append(Spacer(1, 0.1 * inch))

    # Embed purchaser signature image if present
    if contract.purchaser_signature_b64:
        try:
            # Strip data URI prefix if present (data:image/png;base64,...)
            sig_b64 = contract.purchaser_signature_b64
            if "," in sig_b64:
                sig_b64 = sig_b64.split(",", 1)[1]
            sig_bytes = base64.b64decode(sig_b64)
            sig_reader = ImageReader(BytesIO(sig_bytes))
            story.append(Paragraph("<b>Purchaser Signature:</b>", normal))
            story.append(Spacer(1, 0.05 * inch))
            # Draw image inline via a Table cell for layout control
            sig_img_table = Table(
                [[sig_reader]],
                colWidths=[2.5 * inch],
                rowHeights=[0.75 * inch],
            )
            sig_img_table.setStyle(
                TableStyle([
                    ("ALIGN", (0, 0), (-1, -1), "LEFT"),
                    ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                    ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#d1d5db")),
                ])
            )
            story.append(sig_img_table)
        except Exception as sig_err:
            logger.warning("[pdf] Could not embed signature image: %s", sig_err)
            story.append(Paragraph("Purchaser Signature: [on file]", normal))
    else:
        story.append(Paragraph("Purchaser Signature: [on file]", normal))

    story.append(Spacer(1, 0.1 * inch))
    story.append(
        Paragraph(f"<b>Purchaser Name:</b> {contract.purchaser_name or '—'}", normal)
    )
    story.append(Spacer(1, 0.1 * inch))
    story.append(
        Paragraph(f"<b>Witness Name:</b> {contract.witness_name or '—'}", normal)
    )
    story.append(Spacer(1, 0.2 * inch))
    story.append(
        Paragraph(
            "This document constitutes a legally binding agreement between the purchaser "
            "and INDELIS Cemetery upon execution by both parties.",
            small_style,
        )
    )

    doc.build(story)
    pdf_bytes = buf.getvalue()

    # ── 3. Upload to S3 ──────────────────────────────────────────────────────
    s3_key = f"contracts/{tenant_id}/{contract.contract_number or contract_id}.pdf"
    pdf_s3_key_saved = None

    if not settings.AWS_ACCESS_KEY_ID or not settings.AWS_SECRET_ACCESS_KEY:
        logger.warning(
            "[pdf] S3 credentials not configured — skipping upload for contract %s",
            contract_id,
        )
    else:
        try:
            s3 = boto3.client(
                "s3",
                region_name=settings.AWS_REGION,
                aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
                aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
            )
            await asyncio.to_thread(
                s3.put_object,
                Bucket=settings.S3_BUCKET,
                Key=s3_key,
                Body=pdf_bytes,
                ContentType="application/pdf",
            )
            pdf_s3_key_saved = s3_key
            logger.info("[pdf] Uploaded contract PDF to s3://%s/%s", settings.S3_BUCKET, s3_key)
        except (NoCredentialsError, ClientError) as exc:
            logger.warning("[pdf] S3 upload failed for contract %s: %s", contract_id, exc)

    # ── 4. Update contract.pdf_s3_key ────────────────────────────────────────
    if pdf_s3_key_saved:
        async with AsyncSessionLocal() as db:
            result = await db.execute(
                select(Contract).where(
                    Contract.id == contract_uuid,
                    Contract.tenant_id == tenant_uuid,
                    Contract.deleted_at.is_(None),
                )
            )
            c = result.scalar_one_or_none()
            if c:
                c.pdf_s3_key = pdf_s3_key_saved
                await db.commit()

    # ── 5. Send email via SendGrid ────────────────────────────────────────────
    sendgrid_key = getattr(settings, "SENDGRID_API_KEY", "")
    if sendgrid_key and contract.purchaser_email:
        try:
            import sendgrid as sg_module
            from sendgrid.helpers.mail import Mail

            sg = sg_module.SendGridAPIClient(api_key=sendgrid_key)
            message = Mail(
                from_email=settings.SES_FROM_EMAIL,
                to_emails=contract.purchaser_email,
                subject=f"Your contract {contract.contract_number} is ready",
                plain_text_content=(
                    f"Dear {contract.purchaser_name or 'Valued Customer'},\n\n"
                    f"Your signed Cemetery Plot Purchase Agreement "
                    f"(Contract #{contract.contract_number}) has been processed and is ready.\n\n"
                    f"Please retain this confirmation for your records. "
                    f"A copy of your contract PDF is available through your INDELIS portal.\n\n"
                    f"If you have any questions, please contact the cemetery office.\n\n"
                    f"— INDELIS Cemetery Services\n"
                ),
            )
            await asyncio.to_thread(sg.send, message)
            logger.info(
                "[pdf] Contract confirmation email sent to %s", contract.purchaser_email
            )
        except Exception as email_err:
            logger.warning(
                "[pdf] SendGrid email failed for contract %s: %s", contract_id, email_err
            )
    elif contract.purchaser_email:
        logger.info(
            "[pdf] SENDGRID_API_KEY not set — skipping email for contract %s", contract_id
        )

    return {
        "status": "generated",
        "contract_id": contract_id,
        "pdf_uploaded": pdf_s3_key_saved is not None,
    }


async def export_week_pdf(ctx: dict, tenant_id: str, week_start: str):
    """Generate weekly run-sheet PDF → S3 → return presigned URL."""
    logger.info(f"[pdf] Generating week PDF for tenant {tenant_id}, week {week_start}")
    # TODO: ReportLab PDF generation + S3 upload
    return {"status": "generated", "week_start": week_start, "presigned_url": None}


async def send_service_reminder(ctx: dict, service_id: str, tenant_id: str):
    """SES email to family 24h before service."""
    logger.info(f"[email] Sending 24h reminder for service {service_id}")
    # TODO: Integrate SES
    return {"status": "sent", "service_id": service_id}


async def check_overdue_invoices(ctx: dict):
    """Daily job: mark overdue invoices and send 7/14/30-day reminders."""
    logger.info("[scheduler] Running check_overdue_invoices")
    from src.database.session import AsyncSessionLocal
    from src.apps.billing.services.invoice_service import InvoiceService

    async with AsyncSessionLocal() as db:
        service = InvoiceService()
        count = await service.transition_overdue(db)

    logger.info(f"[scheduler] Processed {count} overdue invoices")
    return {"processed": count}


async def startup(ctx: dict):
    logger.info("ARQ worker started")


async def shutdown(ctx: dict):
    logger.info("ARQ worker stopped")


# ── Worker Settings ───────────────────────────────────────────────────────────

class WorkerSettings:
    functions = [
        send_welcome_email,
        send_invoice_reminder,
        generate_qr_code,
        regenerate_all_qr,
        generate_contract_pdf,
        check_overdue_invoices,
        export_week_pdf,
        send_service_reminder,
    ]
    on_startup = startup
    on_shutdown = shutdown
    redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
    max_jobs = 10
    job_timeout = 300  # 5 min
    keep_result = 3600  # 1 hour

    # Scheduled jobs (cron)
    cron_jobs = [
        arq.cron(check_overdue_invoices, hour=0, minute=0),  # 00:00 UTC daily
    ]
