"""
Comprehensive Invoice Service

This service handles invoice creation, PDF generation, accounting integration,
and WhatsApp/email delivery for tax-compliant invoices.
"""

from sqlalchemy.orm import Session
from sqlalchemy import func, desc
from typing import List, Dict, Optional, Tuple, Any
from datetime import datetime, date, timedelta
from decimal import Decimal
import uuid
from io import BytesIO
import os
import base64

# PDF generation
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, inch
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.platypus.flowables import HRFlowable
from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
from reportlab.lib.utils import ImageReader

from app.models.sales import Invoice, InvoiceItem, Customer
from app.models.inventory import Product, InventoryTransaction
from app.models.branch import Branch
from app.models.user import User
from app.models.app_setting import AppSetting
from app.models.invoice_template import InvoiceTemplate
from app.models.accounting import JournalEntry, AccountingEntry, AccountingCode
from app.core.database import get_db
from app.services.dimension_resolver_service import DimensionResolverService


class InvoiceService:
    """Comprehensive invoice management service"""

    def __init__(self, db: Session):
        self.db = db
        self._app_setting_instance: Optional[AppSetting] = None
        self.app_settings = self._load_app_settings()
        self.invoice_designer_config = self._load_invoice_designer_config()

    def _format_user_display(self, user: Optional[User]) -> str:
        """Return a readable, full user name with sensible fallbacks."""
        if not user:
            return "Unknown User"

        parts = [part for part in [user.first_name, user.last_name] if part]
        full_name = " ".join(parts).strip()
        if full_name:
            return full_name

        for fallback in (user.username, user.email):
            if fallback:
                return fallback

        return "Unknown User"

    def _load_app_settings(self) -> Dict:
        """Load application settings for invoice generation"""
        settings: Dict[str, str] = {}
        try:
            app_settings = self.db.query(AppSetting).all()
            # Two possible schemas:
            # 1. Key/Value rows (legacy) where each row has 'key' and 'value' columns
            # 2. Singleton settings row with many explicit columns (current model)
            if app_settings:
                first = app_settings[0]
                if hasattr(first, 'invoice_designer_config'):
                    self._app_setting_instance = first
                if hasattr(first, 'key') and hasattr(first, 'value'):
                    # Legacy style: multiple key/value rows
                    for setting in app_settings:
                        try:
                            settings[str(setting.key)] = str(setting.value)
                        except Exception:
                            continue
                else:
                    # Singleton style: only use the first row; extract column values
                    # Avoid SQLAlchemy internal attributes
                    cols = [c.name for c in first.__table__.columns]
                    for col in cols:
                        try:
                            val = getattr(first, col)
                            # Skip id/meta fields if desired
                            settings[col] = '' if val is None else str(val)
                        except Exception:
                            continue
        except Exception as e:
            # If AppSetting table doesn't exist or query fails, rollback and use defaults
            print(f"[InvoiceService] Warning: Could not load app settings: {e}")
            try:
                self.db.rollback()
            except Exception:
                pass

        # Default values if not set
        defaults = {
            'company_name': 'Your Company Name',
            'address': 'Company Address',
            'phone': 'Phone Number',
            'email': 'email@company.com',
            'website': 'www.company.com',
            'vat_registration_number': 'VAT123456789',
            'currency': 'BWP',
            'default_vat_rate': '12.0',
            'whatsapp_number': '',
            'whatsapp_api_key': '',
            'whatsapp_enabled': 'false'
        }

        for key, default_value in defaults.items():
            if key not in settings:
                settings[key] = default_value

        return settings

    def _load_invoice_designer_config(self) -> Dict[str, Any]:
        """Load saved invoice designer layout configuration."""
        settings_row = self._app_setting_instance

        if not settings_row or not hasattr(settings_row, 'invoice_designer_config'):
            try:
                settings_row = self.db.query(AppSetting).first()
                if settings_row and hasattr(settings_row, 'invoice_designer_config'):
                    self._app_setting_instance = settings_row
            except Exception as e:
                print(f"[InvoiceService] Warning: Could not query AppSetting for designer config: {e}")
                try:
                    self.db.rollback()
                except Exception:
                    pass
                return {}

        if settings_row and hasattr(settings_row, 'invoice_designer_config'):
            try:
                config = settings_row.invoice_designer_config
                if isinstance(config, dict):
                    return config
            except Exception as exc:
                print(f"[INVOICE_DESIGNER_CONFIG_WARN] Failed to load designer config: {exc}")

        return {
            "layout": [],
            "form_data": {},
            "metadata": {},
            "version": 1,
            "updated_at": None,
        }

    def _map_css_font_to_reportlab(self, css_font_family: str) -> str:
        """Map CSS font family to valid ReportLab font names"""
        # Clean up the font family string
        font_family = css_font_family.lower().strip()

        # Handle comma-separated font families (take the first valid one)
        if ',' in font_family:
            fonts = [f.strip() for f in font_family.split(',')]
            font_family = fonts[0]

        # Font mapping from CSS names to ReportLab names
        font_mapping = {
            'arial': 'Helvetica',
            'helvetica': 'Helvetica',
            'times': 'Times-Roman',
            'times new roman': 'Times-Roman',
            'courier': 'Courier',
            'courier new': 'Courier',
            'georgia': 'Times-Roman',
            'verdana': 'Helvetica',
            'tahoma': 'Helvetica',
            'trebuchet ms': 'Helvetica',
            'sans-serif': 'Helvetica',
            'serif': 'Times-Roman',
            'monospace': 'Courier'
        }

        # Return mapped font or default to Helvetica
        return font_mapping.get(font_family, 'Helvetica')

    def _safe_int(self, value, default: int) -> int:
        """Convert to int with a graceful fallback for None/empty/invalid values."""
        try:
            return int(value)
        except (TypeError, ValueError):
            return default

    def _safe_float(self, value, default: float) -> float:
        """Convert to float with a graceful fallback for None/empty/invalid values."""
        try:
            return float(value)
        except (TypeError, ValueError):
            return default

    def _load_template(self, template_id: Optional[str] = None) -> Optional[InvoiceTemplate]:
        """Fetch an invoice template by id, default, or first active."""
        query = self.db.query(InvoiceTemplate).filter(InvoiceTemplate.is_active == True)

        if template_id:
            template = query.filter(InvoiceTemplate.id == template_id).first()
            if template:
                return template

        template = query.filter(InvoiceTemplate.is_default == True).first()
        if template:
            return template

        return query.first()

    def _template_to_settings(self, template: InvoiceTemplate) -> Dict[str, Any]:
        """Map InvoiceTemplate fields onto the existing settings dictionary keys."""
        settings = dict(self.app_settings) if isinstance(self.app_settings, dict) else {}

        # Margins & layout
        settings.update({
            'invoice_margin_top': template.page_margin_top or 20,
            'invoice_margin_bottom': template.page_margin_bottom or 20,
            'invoice_margin_left': template.page_margin_left or 20,
            'invoice_margin_right': template.page_margin_right or 20,
        })

        # Branding & colors
        settings.update({
            'invoice_primary_color': template.primary_color or settings.get('invoice_primary_color', '#007bff'),
            'invoice_secondary_color': template.secondary_color or settings.get('invoice_secondary_color', '#6c757d'),
            'invoice_accent_color': template.accent_color or settings.get('invoice_accent_color', '#28a745'),
            'invoice_background_color': settings.get('invoice_background_color', '#ffffff'),
            'invoice_font_family': template.font_family or settings.get('invoice_font_family', 'Helvetica'),
            'invoice_base_font_size': template.base_font_size or settings.get('invoice_base_font_size', 12),
            'invoice_line_height': template.line_height or settings.get('invoice_line_height', 1.4),
            'invoice_footer_font_size': template.footer_font_size or settings.get('invoice_footer_font_size', 11),
            'invoice_footer_text_color': template.text_color or settings.get('invoice_footer_text_color', '#000000'),
            'invoice_footer_text': template.footer_text or settings.get('invoice_footer_text', ''),
            'invoice_terms_conditions': settings.get('invoice_terms_conditions', ''),
        })

        # Logo and company visibility
        settings.update({
            'invoice_show_logo': template.show_logo,
            'logo_width': template.logo_width_mm,
            'logo_height': template.logo_height_mm,
            'invoice_show_company_address': template.show_company_address,
            'invoice_show_company_phone': template.show_company_phone,
            'invoice_show_company_email': template.show_company_email,
            'invoice_show_company_website': template.show_company_website,
            'invoice_show_vat_number': template.show_company_vat,
            'invoice_show_company_section': template.show_header,
        })

        # Invoice detail visibility
        settings.update({
            'invoice_show_invoice_details': template.show_invoice_details,
            'invoice_show_invoice_number': template.show_invoice_number,
            'invoice_show_invoice_date': template.show_invoice_date,
            'invoice_show_due_date': template.show_due_date,
            'invoice_show_payment_terms': template.show_payment_terms,
            'invoice_show_branch': template.show_branch,
            'invoice_show_generated_at': template.show_generated_at,
        })

        # Customer section visibility & styling
        settings.update({
            'invoice_show_customer_section': template.show_customer,
            'invoice_show_customer_address': template.show_customer_address,
            'invoice_show_customer_phone': template.show_customer_phone,
            'invoice_show_customer_email': template.show_customer_email,
            'invoice_show_customer_vat_number': template.show_customer_vat,
            'customer_section_background': template.customer_section_background_color or '#f8f9fa',
        })

        # Items table styling
        settings.update({
            'items_table_header_bg': template.items_header_background or '#343a40',
            'items_table_header_text': template.items_header_text_color or '#ffffff',
            'items_table_border_color': template.items_border_color or '#dee2e6',
            'items_table_stripe_color': template.items_stripe_color or '#f8f9fa',
            'items_table_font_size': template.items_font_size or 12,
            'invoice_show_discount': template.show_discount,
            'invoice_show_vat_breakdown': template.show_vat,
        })

        # Totals styling & visibility
        settings.update({
            'invoice_show_totals': template.show_totals,
            'invoice_show_subtotal': template.show_subtotal,
            'invoice_show_total_discount': template.show_discount,
            'invoice_show_total_vat': template.show_vat,
            'invoice_show_total_amount': template.show_total,
            'totals_section_background': template.totals_background_color or '#f8f9fa',
            'totals_font_size': template.totals_font_size or 14,
        })

        # Footer visibility
        settings.update({
            'invoice_show_footer': template.show_footer,
            'invoice_show_footer_text': template.show_footer_text,
            'invoice_show_terms_conditions': template.show_terms_conditions,
            'invoice_show_bank_details': template.show_bank_details,
        })

        return settings

    def generate_invoice_number(self, branch_id: str = None) -> str:
        """Generate next sequential invoice number"""
        prefix = self.app_settings.get('invoice_prefix', 'INV')
        start_number = int(self.app_settings.get('invoice_start_number', 1000) or 1000)
        auto_gen = str(self.app_settings.get('auto_generate_invoices', 'true')).lower() == 'true'

        if not auto_gen:
            # Fallback to UUID-based if auto generation disabled
            return f"{prefix}-{uuid.uuid4().hex[:8].upper()}"

        current_year = datetime.now().year
        # Find the highest invoice number for the current year
        year_prefix = f"{prefix}-{current_year}-"
        latest_invoice = self.db.query(Invoice).filter(
            Invoice.invoice_number.like(f"{year_prefix}%")
        ).order_by(desc(Invoice.invoice_number)).first()

        if latest_invoice and latest_invoice.invoice_number:
            try:
                # Extract the sequence number from the invoice number
                seq_part = latest_invoice.invoice_number.replace(year_prefix, "")
                number = int(seq_part) + 1
            except (ValueError, AttributeError):
                number = start_number
        else:
            number = start_number

        # Ensure uniqueness by checking if the generated number already exists
        invoice_number = f"{year_prefix}{number:05d}"
        while self.db.query(Invoice).filter(Invoice.invoice_number == invoice_number).first():
            number += 1
            invoice_number = f"{year_prefix}{number:05d}"

        return invoice_number

    def create_invoice(
        self,
        customer_id: str,
        branch_id: str,
        invoice_items: List[Dict],
        due_date: date = None,
        payment_terms: int = 30,
        discount_percentage: float = 0.0,
        notes: str = "",
        created_by: str = None,
        save_draft: bool = False,
        is_cash_sale: bool = False,
        quotation_id: str = None
    ) -> Invoice:
        """Create a new tax invoice with accounting entries"""

        # Generate invoice number
        invoice_number = self.generate_invoice_number(branch_id)

        # Calculate totals
        subtotal = Decimal('0.00')
        total_vat = Decimal('0.00')
        total_discount = Decimal('0.00')

        # Determine invoice status
        if save_draft:
            status = 'draft'
        else:
            status = 'paid' if is_cash_sale else 'unpaid'
        # Create invoice
        invoice = Invoice(
            customer_id=customer_id,
            branch_id=branch_id,
            invoice_number=invoice_number,
            date=date.today(),
            due_date=due_date or (date.today() + timedelta(days=payment_terms)),
            payment_terms=payment_terms,
            discount_percentage=Decimal(str(discount_percentage)),
            notes=notes,
            created_by=created_by,
            status=status,
            quotation_id=quotation_id
        )

        self.db.add(invoice)
        self.db.flush()  # Get invoice ID

        # Add invoice items
        for item_data in invoice_items:
            product = self.db.query(Product).filter(Product.id == item_data['product_id']).first()
            if not product:
                continue

            quantity = Decimal(str(item_data['quantity']))
            # Support both 'price' and 'unit_price' field names for backward compatibility
            unit_price = Decimal(str(item_data.get('price') or item_data.get('unit_price', product.selling_price)))
            discount_percent = Decimal(str(item_data.get('discount_percentage', 0)))
            vat_rate = Decimal(str(item_data.get('vat_rate', self.app_settings['default_vat_rate'])))

            # Calculate amounts
            line_total = quantity * unit_price
            line_discount = line_total * (discount_percent / 100)
            line_subtotal = line_total - line_discount
            line_vat = line_subtotal * (vat_rate / 100)
            line_total_with_vat = line_subtotal + line_vat

            invoice_item = InvoiceItem(
                invoice_id=invoice.id,
                product_id=item_data['product_id'],
                quantity=int(quantity),
                price=unit_price,  # Fixed: Changed from unit_price to price to match the database model
                total=line_total_with_vat,  # Fixed: Changed from total_amount to total
                vat_amount=line_vat,
                vat_rate=vat_rate,
                discount_amount=line_discount,
                discount_percentage=discount_percent,
                description=item_data.get('description', product.name),
                category=product.category,
                brand=product.brand,
                condition=product.condition,
                location=product.location,
            )

            self.db.add(invoice_item)

            subtotal += line_subtotal
            total_vat += line_vat
            total_discount += line_discount

        # Apply overall discount
        if discount_percentage > 0:
            overall_discount = subtotal * (Decimal(str(discount_percentage)) / 100)
            subtotal -= overall_discount
            total_discount += overall_discount
            # Recalculate VAT on discounted amount
            total_vat = subtotal * (Decimal(str(self.app_settings['default_vat_rate'])) / 100)

        # Update invoice totals
        invoice.subtotal = subtotal
        invoice.total_vat_amount = total_vat
        invoice.discount_amount = total_discount
        invoice.total_amount = subtotal + total_vat
        invoice.amount_due = invoice.total_amount

        # Only create accounting entries when not saving draft
        if not save_draft:
            self._create_invoice_accounting_entries(invoice)

        self.db.commit()
        return invoice

    def get_printer_settings(self) -> Dict:
        """Invoice printer settings from persisted meta_data (Settings → Printers)."""
        from app.services.printer_settings_service import PrinterSettingsService

        section = PrinterSettingsService(self.db).get_section("invoice")
        return {
            "invoice_printer_type": section.get("invoice_printer_type", "a4_pdf"),
            "default_printer_name": section.get("default_printer_name", ""),
            "auto_print": section.get("auto_print", False),
            "dot_matrix_paper_width": str(section.get("dot_matrix_paper_width", 80)),
            "dot_matrix_form_length": str(section.get("dot_matrix_form_length", 66)),
            "dot_matrix_compressed": section.get("dot_matrix_compressed", False),
            "dot_matrix_carbon_copies": str(section.get("dot_matrix_carbon_copies", 1)),
            "dot_matrix_template": section.get("dot_matrix_template", "standard_80"),
            "a4_orientation": section.get("a4_orientation", "portrait"),
            "a4_paper_size": section.get("a4_paper_size", "A4"),
        }

    def generate_invoice_by_printer_type(self, invoice_id: str) -> Tuple[str, bytes, str]:
        """
        Generate invoice in the format specified by INVOICE printer settings
        This method is ONLY for invoices and uses invoice-specific printer configuration
        Returns: (format_type, content, filename)
        """
        printer_settings = self.get_printer_settings()
        printer_type = printer_settings['invoice_printer_type']

        invoice = self.db.query(Invoice).filter(Invoice.id == invoice_id).first()
        if not invoice:
            raise ValueError("Invoice not found")

        customer_name = invoice.customer.name if invoice.customer else "Walk-in_Customer"
        from app.services.document_pdf_storage import build_document_filename
        pdf_filename = build_document_filename(
            "invoice",
            customer_name,
            invoice.invoice_date or invoice.created_at,
            invoice.invoice_number or invoice_id[:8],
        )

        # INVOICE PRINTER SETTINGS - Only applies to invoices
        if printer_type == 'a4_pdf':
            pdf_buffer = self.generate_pdf_invoice(invoice_id)
            content = pdf_buffer.read()
            return ('pdf', content, pdf_filename)

        elif printer_type == 'dot_matrix':
            # Generate dot matrix format for invoices (continuous paper)
            from app.services.dot_matrix_invoice_service import DotMatrixInvoiceService

            dot_matrix_service = DotMatrixInvoiceService(self.db)

            # Load custom template if specified
            custom_template = None
            template_name = printer_settings.get('dot_matrix_template')
            if template_name and template_name != 'standard_80':
                template_setting = self.db.query(AppSetting).filter(
                    AppSetting.key == f"dot_matrix_template_{template_name}"
                ).first()
                if template_setting:
                    custom_template = template_setting.value

            # Generate with saved invoice printer settings
            dot_matrix_content = dot_matrix_service.generate_dot_matrix_invoice(
                invoice_id=invoice_id,
                paper_width=int(printer_settings['dot_matrix_paper_width']),
                form_length=int(printer_settings['dot_matrix_form_length']),
                compressed=(
                    printer_settings['dot_matrix_compressed']
                    if isinstance(printer_settings['dot_matrix_compressed'], bool)
                    else str(printer_settings['dot_matrix_compressed']).lower() in ('true', '1', 'yes')
                ),
                carbon_copies=int(printer_settings['dot_matrix_carbon_copies']),
                custom_template=custom_template
            )

            content = dot_matrix_content.encode('ascii', errors='replace')
            filename = f"Invoice_{invoice.invoice_number}_DotMatrix.txt"
            return ('text', content, filename)

        else:
            # For invoices, fallback to A4 PDF (not thermal receipt)
            # Thermal receipts are only for POS, not invoices
            pdf_buffer = self.generate_pdf_invoice(invoice_id)
            content = pdf_buffer.read()
            return ('pdf', content, pdf_filename)

    def generate_document_a4_pdf(self, document_type: str, document_id: str) -> Tuple[str, bytes, str]:
        """
        Generate any other document (NOT invoice, NOT POS receipt) as A4 PDF
        This is for all other documents that should always print on plain A4 paper
        """
        if document_type == 'invoice':
            # Invoices should use invoice printer settings
            return self.generate_invoice_by_printer_type(document_id)

        # For all other documents, always use A4 PDF regardless of printer settings
        # Examples: Reports, statements, quotes, purchase orders, etc.

        if document_type == 'quote':
            # Generate quote as A4 PDF
            pdf_buffer = self._generate_quote_pdf(document_id)
        elif document_type == 'purchase_order':
            # Generate purchase order as A4 PDF
            pdf_buffer = self._generate_purchase_order_pdf(document_id)
        elif document_type == 'statement':
            # Generate statement as A4 PDF
            pdf_buffer = self._generate_statement_pdf(document_id)
        elif document_type == 'report':
            # Generate report as A4 PDF
            pdf_buffer = self._generate_report_pdf(document_id)
        else:
            # Generic document as A4 PDF
            pdf_buffer = self._generate_generic_pdf(document_type, document_id)

        content = pdf_buffer.read()
        filename = f"{document_type.title()}_{document_id}.pdf"
        return ('pdf', content, filename)

    def _generate_quote_pdf(self, quote_id: str) -> BytesIO:
        """Generate quote as A4 PDF - always plain paper"""
        # Quotes are typically invoices with status 'draft' or 'quote'
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4

        quote = self.db.query(Invoice).filter(Invoice.id == quote_id).first()

        if not quote:
            return self._generate_placeholder_pdf("Quote", quote_id)

        # Generate similar to invoice but labeled as Quote
        buffer = BytesIO()
        c = canvas.Canvas(buffer, pagesize=A4)
        width, height = A4

        # Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(50, height - 50, "QUOTATION")

        c.setFont("Helvetica", 10)
        c.drawString(50, height - 80, f"Quote Number: {quote.invoice_number or quote.id}")
        c.drawString(50, height - 95, f"Date: {quote.date}")
        c.drawString(50, height - 110, f"Valid Until: {quote.due_date or 'N/A'}")

        # Customer info
        if quote.customer:
            c.setFont("Helvetica-Bold", 12)
            c.drawString(50, height - 145, "Customer:")
            c.setFont("Helvetica", 10)
            c.drawString(50, height - 160, quote.customer.name)

        # Items
        y_position = height - 200
        c.setFont("Helvetica-Bold", 10)
        c.drawString(50, y_position, "Description")
        c.drawString(350, y_position, "Qty")
        c.drawString(420, y_position, "Price")
        c.drawString(500, y_position, "Total")

        c.setFont("Helvetica", 9)
        y_position -= 20

        items = self.db.query(InvoiceItem).filter(
            InvoiceItem.invoice_id == quote_id
        ).all()

        for item in items:
            if y_position < 100:
                c.showPage()
                y_position = height - 50

            product_desc = item.product.name if item.product else item.description or "Item"
            c.drawString(50, y_position, product_desc[:45])
            c.drawString(350, y_position, str(item.quantity))
            c.drawString(420, y_position, f"P {item.price:.2f}")
            c.drawString(500, y_position, f"P {item.total:.2f}")
            y_position -= 15

        # Totals
        y_position -= 30
        c.setFont("Helvetica-Bold", 10)
        if quote.vat_amount and quote.vat_amount > 0:
            c.drawString(400, y_position, "Subtotal:")
            c.drawString(500, y_position, f"P {(quote.total_amount - quote.vat_amount):.2f}")
            y_position -= 15
            c.drawString(400, y_position, "VAT:")
            c.drawString(500, y_position, f"P {quote.vat_amount:.2f}")
            y_position -= 15

        c.drawString(400, y_position, "Total:")
        c.drawString(500, y_position, f"P {quote.total_amount:.2f}")

        c.save()
        buffer.seek(0)
        return buffer

    def _generate_purchase_order_pdf(self, po_id: str) -> BytesIO:
        """Generate purchase order as A4 PDF - always plain paper"""
        from app.models.purchases import PurchaseOrder, PurchaseOrderItem
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4
        from reportlab.lib.units import mm

        po = self.db.query(PurchaseOrder).filter(PurchaseOrder.id == po_id).first()

        if not po:
            return self._generate_placeholder_pdf("Purchase Order", po_id)

        buffer = BytesIO()
        c = canvas.Canvas(buffer, pagesize=A4)
        width, height = A4

        # Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(50, height - 50, "PURCHASE ORDER")

        c.setFont("Helvetica", 10)
        c.drawString(50, height - 80, f"PO Number: {po.po_number or po.id}")
        c.drawString(50, height - 95, f"Date: {po.date}")
        c.drawString(50, height - 110, f"Status: {po.status}")

        if po.expected_delivery_date:
            c.drawString(50, height - 125, f"Expected Delivery: {po.expected_delivery_date}")

        # Supplier info
        if po.supplier:
            c.setFont("Helvetica-Bold", 12)
            c.drawString(50, height - 160, "Supplier:")
            c.setFont("Helvetica", 10)
            c.drawString(50, height - 175, po.supplier.name)
            if hasattr(po.supplier, 'contact_person') and po.supplier.contact_person:
                c.drawString(50, height - 190, f"Contact: {po.supplier.contact_person}")

        # Items table
        y_position = height - 230
        c.setFont("Helvetica-Bold", 10)
        c.drawString(50, y_position, "Item")
        c.drawString(300, y_position, "Quantity")
        c.drawString(400, y_position, "Unit Cost")
        c.drawString(500, y_position, "Total")

        c.setFont("Helvetica", 9)
        y_position -= 20

        items = self.db.query(PurchaseOrderItem).filter(
            PurchaseOrderItem.purchase_order_id == po_id
        ).all()

        for item in items:
            if y_position < 100:
                c.showPage()
                y_position = height - 50

            product_name = item.product.name if item.product else item.description or "Unknown"
            c.drawString(50, y_position, product_name[:40])
            c.drawString(300, y_position, str(item.quantity))
            c.drawString(400, y_position, f"P {item.unit_cost:.2f}")
            c.drawString(500, y_position, f"P {item.total_cost:.2f}")
            y_position -= 15

        # Totals
        y_position -= 20
        c.setFont("Helvetica-Bold", 10)
        if po.total_vat_amount and po.total_vat_amount > 0:
            c.drawString(400, y_position, "Subtotal:")
            c.drawString(500, y_position, f"P {(po.total_amount - po.total_vat_amount):.2f}")
            y_position -= 15
            c.drawString(400, y_position, "VAT:")
            c.drawString(500, y_position, f"P {po.total_vat_amount:.2f}")
            y_position -= 15

        c.drawString(400, y_position, "Total:")
        c.drawString(500, y_position, f"P {po.total_amount:.2f}")

        # Notes
        if po.notes:
            y_position -= 40
            c.setFont("Helvetica", 9)
            c.drawString(50, y_position, "Notes:")
            y_position -= 15
            # Wrap long notes
            note_lines = po.notes.split('\n')[:5]
            for line in note_lines:
                c.drawString(50, y_position, line[:80])
                y_position -= 12

        c.save()
        buffer.seek(0)
        return buffer

    def _generate_statement_pdf(self, statement_id: str) -> BytesIO:
        """Generate customer statement PDF - shows account activity"""
        # Statement ID format expected: customer_id or statement_id
        from app.models.sales import Invoice
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4

        # Try to find customer
        customer = self.db.query(Customer).filter(Customer.id == statement_id).first()

        if not customer:
            return self._generate_placeholder_pdf("Statement", statement_id)

        buffer = BytesIO()
        c = canvas.Canvas(buffer, pagesize=A4)
        width, height = A4

        # Header
        c.setFont("Helvetica-Bold", 18)
        c.drawString(50, height - 50, "CUSTOMER STATEMENT")

        c.setFont("Helvetica", 10)
        c.drawString(50, height - 80, f"Customer: {customer.name}")
        if hasattr(customer, 'email') and customer.email:
            c.drawString(50, height - 95, f"Email: {customer.email}")
        if hasattr(customer, 'phone') and customer.phone:
            c.drawString(50, height - 110, f"Phone: {customer.phone}")

        c.drawString(50, height - 130, f"Statement Date: {datetime.now().strftime('%Y-%m-%d')}")

        # Get invoices for this customer
        invoices = self.db.query(Invoice).filter(
            Invoice.customer_id == customer.id
        ).order_by(desc(Invoice.date)).limit(50).all()

        # Transaction table
        y_position = height - 170
        c.setFont("Helvetica-Bold", 9)
        c.drawString(50, y_position, "Date")
        c.drawString(130, y_position, "Invoice #")
        c.drawString(230, y_position, "Description")
        c.drawString(400, y_position, "Amount")
        c.drawString(480, y_position, "Balance")

        c.setFont("Helvetica", 8)
        y_position -= 20

        running_balance = Decimal(0)
        for inv in invoices:
            if y_position < 100:
                c.showPage()
                y_position = height - 50

            running_balance += inv.total_amount
            c.drawString(50, y_position, str(inv.date))
            c.drawString(130, y_position, inv.invoice_number or inv.id[:8])
            c.drawString(230, y_position, f"Invoice - {inv.status}")
            c.drawString(400, y_position, f"P {inv.total_amount:.2f}")
            c.drawString(480, y_position, f"P {running_balance:.2f}")
            y_position -= 15

        # Summary
        y_position -= 30
        c.setFont("Helvetica-Bold", 10)
        c.drawString(400, y_position, "Total Outstanding:")
        c.drawString(500, y_position, f"P {running_balance:.2f}")

        c.save()
        buffer.seek(0)
        return buffer

    def _generate_report_pdf(self, report_id: str) -> BytesIO:
        """Generate generic report as A4 PDF"""
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4

        # Generic report generation - can be extended based on report type
        buffer = BytesIO()
        c = canvas.Canvas(buffer, pagesize=A4)
        width, height = A4

        c.setFont("Helvetica-Bold", 18)
        c.drawString(50, height - 50, "REPORT")

        c.setFont("Helvetica", 10)
        c.drawString(50, height - 80, f"Report ID: {report_id}")
        c.drawString(50, height - 95, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

        c.setFont("Helvetica", 9)
        c.drawString(50, height - 130, "This is a generic report document.")
        c.drawString(50, height - 145, "Report details and data would appear here based on report type.")
        c.drawString(50, height - 160, "For specific report formats, use the dedicated report endpoints.")

        c.save()
        buffer.seek(0)
        return buffer

    def _generate_generic_pdf(self, doc_type: str, doc_id: str) -> BytesIO:
        """Generate generic document as A4 PDF - always plain paper"""
        return self._generate_placeholder_pdf(doc_type.title(), doc_id)

    def _generate_placeholder_pdf(self, doc_type: str, doc_id: str) -> BytesIO:
        """Generate placeholder PDF for document types not yet implemented"""
        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4

        buffer = BytesIO()
        doc = canvas.Canvas(buffer, pagesize=A4)

        # Simple placeholder content
        doc.setTitle(f"{doc_type} {doc_id}")
        doc.drawString(100, 750, f"{doc_type} Document")
        doc.drawString(100, 720, f"Document ID: {doc_id}")
        doc.drawString(100, 690, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        doc.drawString(100, 660, "This document is generated on A4 plain paper")
        doc.drawString(100, 630, "(Printer settings do not affect this document type)")

        doc.save()
        buffer.seek(0)
        return buffer



    def _create_invoice_accounting_entries(self, invoice: Invoice):
        """Create journal entries for invoice"""
        resolver = DimensionResolverService(self.db)
        entries_for_dimensions = []

        # Create accounting entry header first
        accounting_entry = AccountingEntry(
            date_prepared=date.today(),
            date_posted=date.today(),
            particulars=f"Tax Invoice {invoice.invoice_number}",
            book="Sales Journal",
            status="posted",
            branch_id=invoice.branch_id
        )
        self.db.add(accounting_entry)
        self.db.flush()  # Get accounting entry ID

        # Get accounting codes
        accounts_receivable = self.db.query(AccountingCode).filter(
            AccountingCode.code == "1200"  # Accounts Receivable
        ).first()

        sales_revenue = self.db.query(AccountingCode).filter(
            AccountingCode.code == "4000"  # Sales Revenue
        ).first()

        vat_payable = self.db.query(AccountingCode).filter(
            AccountingCode.code == "2300"  # VAT Payable
        ).first()

        # Track total cost of goods for COGS/Inventory entry after line loop
        total_cogs = Decimal('0.00')
        # Build cost & inventory transactions per item
        try:
            for item in invoice.invoice_items:
                # Ensure product loaded
                product = None
                try:
                    product = self.db.query(Product).filter(Product.id == item.product_id).first()
                except Exception:
                    product = None
                if not product:
                    continue
                # Compute cost (simple FIFO/standard: cost_price * qty)
                line_cost = (product.cost_price or 0) * item.quantity
                try:
                    total_cogs += Decimal(str(line_cost))
                except Exception:
                    pass
                # Reduce on-hand quantity (avoid negative if not allowed later)
                try:
                    if product.quantity is not None:
                        product.quantity = max(0, (product.quantity or 0) - item.quantity)
                except Exception:
                    pass
                # Inventory transaction record
                inv_tx = InventoryTransaction(
                    product_id=product.id,
                    transaction_type='sale',
                    quantity=item.quantity,
                    unit_cost=product.cost_price or 0,
                    total_cost=line_cost,
                    date=date.today(),
                    reference=f"Invoice {invoice.invoice_number}",
                    branch_id=invoice.branch_id,
                    serial_numbers=[],  # empty list for PostgreSQL TEXT[] column
                    previous_quantity=None,
                    new_quantity=product.quantity
                )
                self.db.add(inv_tx)
        except Exception as inv_err:
            print(f"[INVOICE_INV_TX_WARN] {inv_err}")

        # Debit: Accounts Receivable
        if accounts_receivable:
            debit_entry = JournalEntry(
                accounting_entry_id=accounting_entry.id,
                accounting_code_id=accounts_receivable.id,
                entry_type="debit",
                narration=f"Invoice {invoice.invoice_number} - {invoice.customer.name if hasattr(invoice, 'customer') else 'Customer'}",
                debit_amount=float(invoice.total_amount),
                credit_amount=0.0,
                description=f"Sales to customer",
                reference=invoice.invoice_number,
                date=date.today(),
                branch_id=invoice.branch_id
            )
            self.db.add(debit_entry)
            entries_for_dimensions.append(debit_entry)

        # Credit: Sales Revenue
        if sales_revenue:
            credit_entry = JournalEntry(
                accounting_entry_id=accounting_entry.id,
                accounting_code_id=sales_revenue.id,
                entry_type="credit",
                narration=f"Sales Revenue - Invoice {invoice.invoice_number}",
                debit_amount=0.0,
                credit_amount=float(invoice.subtotal),
                description="Sales revenue",
                reference=invoice.invoice_number,
                date=date.today(),
                branch_id=invoice.branch_id
            )
            self.db.add(credit_entry)
            entries_for_dimensions.append(credit_entry)

        # Credit: VAT Payable
        if vat_payable and invoice.total_vat_amount > 0:
            vat_entry = JournalEntry(
                accounting_entry_id=accounting_entry.id,
                accounting_code_id=vat_payable.id,
                entry_type="credit",
                narration=f"VAT on Invoice {invoice.invoice_number}",
                debit_amount=0.0,
                credit_amount=float(invoice.total_vat_amount),
                description="VAT payable",
                reference=invoice.invoice_number,
                date=date.today(),
                branch_id=invoice.branch_id
            )
            self.db.add(vat_entry)
            entries_for_dimensions.append(vat_entry)

        # Add COGS / Inventory entries if cost captured
        if total_cogs > 0:
            cogs_account = self.db.query(AccountingCode).filter(AccountingCode.code == "5100").first()
            inventory_account = self.db.query(AccountingCode).filter(AccountingCode.code == "1140").first()
            if cogs_account and inventory_account:
                # Debit COGS (Expense)
                cogs_entry = JournalEntry(
                    accounting_entry_id=accounting_entry.id,
                    accounting_code_id=cogs_account.id,
                    entry_type="debit",
                    narration=f"COGS for Invoice {invoice.invoice_number}",
                    debit_amount=float(total_cogs),
                    credit_amount=0.0,
                    description="Cost of goods sold",
                    reference=invoice.invoice_number,
                    date=date.today(),
                    branch_id=invoice.branch_id
                )
                self.db.add(cogs_entry)
                entries_for_dimensions.append(cogs_entry)
                # Credit Inventory (Asset decrease)
                inv_entry = JournalEntry(
                    accounting_entry_id=accounting_entry.id,
                    accounting_code_id=inventory_account.id,
                    entry_type="credit",
                    narration=f"Inventory reduction for Invoice {invoice.invoice_number}",
                    debit_amount=0.0,
                    credit_amount=float(total_cogs),
                    description="Inventory outflow",
                    reference=invoice.invoice_number,
                    date=date.today(),
                    branch_id=invoice.branch_id
                )
                self.db.add(inv_entry)
                entries_for_dimensions.append(inv_entry)

        # Ensure IDs are available for assignments
        self.db.flush()

        resolver.apply_from_document(entries_for_dimensions, invoice)

    def generate_pdf_invoice(self, invoice_id: str, template_id: Optional[str] = None) -> BytesIO:
        """Generate fully customizable A4 PDF invoice using template-backed settings."""
        from sqlalchemy.orm import joinedload
        from app.models.sales import InvoiceItem

        # Load invoice with all necessary relationships
        invoice = self.db.query(Invoice).options(
            joinedload(Invoice.customer),
            joinedload(Invoice.branch),
            joinedload(Invoice.created_by_user),
            joinedload(Invoice.invoice_items).joinedload(InvoiceItem.product)
        ).filter(Invoice.id == invoice_id).first()

        if not invoice:
            raise ValueError("Invoice not found")

        # Load template (if provided or default) and map to settings
        template = self._load_template(template_id)
        settings = self._template_to_settings(template) if template else self.app_settings

        # Create PDF buffer with customizable margins
        buffer = BytesIO()
        paper_size = A4  # Could be customizable based on settings.get('invoice_paper_size')
        margin_top = self._safe_int(settings.get('invoice_margin_top', 20), 20) * mm
        margin_bottom = self._safe_int(settings.get('invoice_margin_bottom', 20), 20) * mm
        margin_left = self._safe_int(settings.get('invoice_margin_left', 20), 20) * mm
        margin_right = self._safe_int(settings.get('invoice_margin_right', 20), 20) * mm

        doc = SimpleDocTemplate(
            buffer,
            pagesize=paper_size,
            rightMargin=margin_right,
            leftMargin=margin_left,
            topMargin=margin_top,
            bottomMargin=margin_bottom
        )

        # Build PDF content
        story = []
        styles = getSampleStyleSheet()

        # Get color settings
        primary_color = colors.HexColor(settings.get('invoice_primary_color', '#007bff'))
        secondary_color = colors.HexColor(settings.get('invoice_secondary_color', '#6c757d'))
        accent_color = colors.HexColor(settings.get('invoice_accent_color', '#28a745'))
        bg_color = colors.HexColor(settings.get('invoice_background_color', '#ffffff'))

        # Get typography settings with font family mapping
        css_font_family = settings.get('invoice_font_family', 'Helvetica')
        font_family = self._map_css_font_to_reportlab(css_font_family)
        base_font_size = self._safe_int(settings.get('invoice_base_font_size', 12), 12)
        line_height = float(settings.get('invoice_line_height', 1.4))

        # Custom styles with settings
        title_style = ParagraphStyle(
            'CustomTitle',
            parent=styles['Heading1'],
            fontSize=self._safe_int(settings.get('invoice_title_font_size', 36), 36),
            fontName=f"{font_family}-Bold" if settings.get('invoice_title_font_weight') == 'bold' else font_family,
            spaceAfter=30,
            alignment=getattr(TA_CENTER if settings.get('invoice_title_alignment') == 'center'
                           else TA_RIGHT if settings.get('invoice_title_alignment') == 'right'
                           else TA_LEFT, 'value', TA_RIGHT),
            textColor=colors.HexColor(settings.get('invoice_title_color', '#333333'))
        )

        header_style = ParagraphStyle(
            'CustomHeader',
            parent=styles['Heading2'],
            fontSize=14,
            spaceAfter=12,
            fontName=f"{font_family}-Bold",
            textColor=colors.HexColor(settings.get('invoice_header_text_color', '#000000'))
        )

        company_style = ParagraphStyle(
            'CompanyStyle',
            parent=styles['Normal'],
            fontSize=self._safe_int(settings.get('company_info_font_size', 12), 12),
            fontName=f"{font_family}-Bold" if settings.get('company_info_font_weight') == 'bold' else font_family,
            textColor=colors.HexColor(settings.get('company_info_color', '#000000')),
            alignment=getattr(TA_CENTER if settings.get('company_info_alignment') == 'center'
                           else TA_RIGHT if settings.get('company_info_alignment') == 'right'
                           else TA_LEFT, 'value', TA_LEFT)
        )

        # Logo handling - support base64 data URL or file path
        logo_image = None

        # Prefer base64 logo if provided (works even when company_logo_url is an API path)
        logo_b64 = settings.get('company_logo_base64')
        if logo_b64:
            try:
                # Handle data URLs (e.g., data:image/png;base64,...) or plain base64
                if ',' in logo_b64:
                    logo_b64 = logo_b64.split(',', 1)[1]
                logo_bytes = base64.b64decode(logo_b64 + '===')  # tolerate missing padding
                logo_image = BytesIO(logo_bytes)
            except Exception:
                logo_image = None

        # Fallback to logo URL/path (supports http/https or filesystem)
        if not logo_image and settings.get('company_logo_url'):
            logo_path = settings.get('company_logo_url')
            try:
                if logo_path.startswith('http://') or logo_path.startswith('https://'):
                    logo_image = logo_path  # Pass URL directly to Image()
                elif os.path.exists(logo_path):
                    logo_image = logo_path  # Pass file path directly to Image()
            except Exception:
                logo_image = None

        if settings.get('invoice_show_company_section', True):
            # Logo and company info layout - side by side
            company_info_parts = [f"<b>{settings.get('company_name', 'Your Company Name')}</b>"]

            if settings.get('invoice_show_company_address', True):
                address = settings.get('address', 'Company Address')
                if address:
                    company_info_parts.append(address)

            if settings.get('invoice_show_company_phone', True):
                phone = settings.get('phone', '')
                if phone:
                    company_info_parts.append(f"Phone: {phone}")

            if settings.get('invoice_show_company_email', True):
                email = settings.get('email', '')
                if email:
                    company_info_parts.append(f"Email: {email}")

            if settings.get('invoice_show_company_website', True):
                website = settings.get('website', '')
                if website:
                    company_info_parts.append(f"Website: {website}")

            if settings.get('invoice_show_vat_number', True):
                vat_number = settings.get('vat_registration_number', '')
                if vat_number:
                    company_info_parts.append(f"VAT Number: {vat_number}")

            company_info = "<br/>".join(company_info_parts)

            if settings.get('invoice_show_logo', True) and logo_image:
                logo_width = self._safe_int(settings.get('logo_width', 80), 80) * mm / 10
                logo_height = self._safe_int(settings.get('logo_height', 80), 80) * mm / 10
                logo = Image(logo_image, width=logo_width, height=logo_height)

                header_table_data = [[logo, Paragraph(company_info, company_style)]]
                available_width = doc.width
                logo_col_width = min(60 * mm, available_width * 0.3)
                info_col_width = max(available_width - logo_col_width, 50 * mm)
                header_table = Table(header_table_data, colWidths=[logo_col_width, info_col_width])
                header_table.setStyle(TableStyle([
                    ('ALIGN', (0, 0), (0, 0), 'LEFT'),
                    ('ALIGN', (1, 0), (1, 0), 'LEFT'),
                    ('VALIGN', (0, 0), (0, 0), 'TOP'),
                    ('VALIGN', (1, 0), (1, 0), 'TOP'),
                    ('LEFTPADDING', (0, 0), (0, 0), 0),
                    ('RIGHTPADDING', (0, 0), (0, 0), 10),
                    ('TOPPADDING', (0, 0), (0, 0), 0),
                    ('BOTTOMPADDING', (0, 0), (0, 0), 0),
                    ('LEFTPADDING', (1, 0), (1, 0), 10),
                    ('RIGHTPADDING', (1, 0), (1, 0), 0),
                    ('TOPPADDING', (1, 0), (1, 0), 0),
                    ('BOTTOMPADDING', (1, 0), (1, 0), 0),
                ]))
                story.append(header_table)
            else:
                story.append(Paragraph(company_info, company_style))

            story.append(Spacer(1, 25))

        invoice_title = settings.get('invoice_title_text', 'INVOICE')
        story.append(Paragraph(invoice_title, title_style))

        border_style = settings.get('invoice_header_border_style', 'solid')
        border_width = self._safe_int(settings.get('invoice_header_border_width', 2), 2)
        border_color = colors.HexColor(settings.get('invoice_header_border_color', '#333333'))

        if border_style != 'none':
            story.append(HRFlowable(width="100%", thickness=border_width, color=border_color))
        story.append(Spacer(1, 30))

        generated_dt = getattr(invoice, 'created_at', None) or datetime.now()
        generated_str = generated_dt.strftime('%d/%m/%Y %H:%M:%S')
        branch_line = None
        branch_address = None
        if invoice.branch:
            branch_name = invoice.branch.name or 'Branch'
            branch_code = invoice.branch.code
            branch_line = f"{branch_name}{' (' + branch_code + ')' if branch_code else ''}"
            branch_address = invoice.branch.address

        creator_name = self._format_user_display(getattr(invoice, 'created_by_user', None))
        creator_username = getattr(getattr(invoice, 'created_by_user', None), 'username', None)
        prepared_by = creator_name if not creator_username or creator_username in creator_name else f"{creator_name} ({creator_username})"

        if settings.get('invoice_show_invoice_details', True):
            invoice_details = []
            details_value_style = ParagraphStyle(
                'InvoiceDetailsValue',
                parent=styles['Normal'],
                fontSize=base_font_size,
                fontName=font_family,
                leading=base_font_size * line_height
            )

            def _details_value(val: str) -> Paragraph:
                return Paragraph(str(val), details_value_style)

            if settings.get('invoice_show_invoice_number', True):
                invoice_details.append([
                    'Invoice Number:',
                    _details_value(invoice.invoice_number),
                    'Invoice Date:' if settings.get('invoice_show_invoice_date', True) else '',
                    _details_value(invoice.date.strftime('%d/%m/%Y') if invoice.date else 'N/A')
                ])
            else:
                if settings.get('invoice_show_invoice_date', True):
                    invoice_details.append([
                        'Invoice Date:',
                        _details_value(invoice.date.strftime('%d/%m/%Y') if invoice.date else 'N/A'),
                        '',
                        ''
                    ])

            if settings.get('invoice_show_generated_at', True):
                invoice_details.append([
                    'Generated At:',
                    _details_value(generated_str),
                    'Branch:' if settings.get('invoice_show_branch', True) else '',
                    _details_value(branch_line or 'N/A')
                ])
            elif settings.get('invoice_show_branch', True):
                invoice_details.append([
                    'Branch:',
                    _details_value(branch_line or 'N/A'),
                    'Prepared By:',
                    _details_value(prepared_by)
                ])

            invoice_details.append([
                'Prepared By:',
                _details_value(prepared_by),
                'Branch Address:',
                _details_value(branch_address or 'N/A')
            ])

            if settings.get('invoice_show_due_date', True) and invoice.due_date:
                invoice_details.append([
                    'Due Date:',
                    _details_value(invoice.due_date.strftime('%d/%m/%Y')),
                    '',
                    ''
                ])

            if settings.get('invoice_show_payment_terms', True):
                payment_terms = invoice.payment_terms or self._safe_int(settings.get('default_payment_terms', 30), 30)
                invoice_details.append([
                    'Payment Terms:',
                    _details_value(f'{payment_terms} days'),
                    '',
                    ''
                ])

            details_weights = [30, 60, 25, 60]
            details_total = sum(details_weights)
            details_col_widths = [doc.width * (w / details_total) for w in details_weights]
            details_table = Table(invoice_details, colWidths=details_col_widths)
            details_table.setStyle(TableStyle([
                ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
                ('FONTNAME', (0, 0), (0, -1), f'{font_family}-Bold'),
                ('FONTNAME', (2, 0), (2, -1), f'{font_family}-Bold'),
                ('FONTSIZE', (0, 0), (-1, -1), base_font_size),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
                ('WORDWRAP', (0, 0), (-1, -1), 'CJK'),
                ('VALIGN', (0, 0), (-1, -1), 'TOP'),
            ]))

            story.append(details_table)
            story.append(Spacer(1, 30))

        if settings.get('invoice_show_customer_section', True):
            customer = invoice.customer
            story.append(Paragraph("Bill To:", header_style))

            customer_bg = colors.HexColor(settings.get('customer_section_background', '#f8f9fa'))
            customer_border = settings.get('customer_section_border', True)
            customer_border_color = colors.HexColor(settings.get('customer_section_border_color', '#dee2e6'))

            customer_info_parts = [f"<b>{getattr(customer, 'name', 'Walk-in Customer')}</b>"]

            if settings.get('invoice_show_customer_address', True):
                addr_val = getattr(customer, 'address', None)
                if addr_val:
                    customer_info_parts.append(addr_val)

            if settings.get('invoice_show_customer_phone', True):
                phone_val = getattr(customer, 'phone', None)
                if phone_val:
                    customer_info_parts.append(f"Phone: {phone_val}")

            if settings.get('invoice_show_customer_email', True):
                email_val = getattr(customer, 'email', None)
                if email_val:
                    customer_info_parts.append(f"Email: {email_val}")

            if settings.get('invoice_show_customer_vat_number', True):
                vat_val = getattr(customer, 'vat_reg_number', None)
                if vat_val:
                    customer_info_parts.append(f"VAT Number: {vat_val}")

            customer_info = "<br/>".join(customer_info_parts)
            story.append(Paragraph(customer_info, styles['Normal']))
            story.append(Spacer(1, 30))

        # Invoice items table with customizable styling
        story.append(Paragraph("Invoice Items:", header_style))

        template_columns = template.get_items_columns() if template else []

        headers = ['Product/Description', 'Qty', 'Unit Price']
        if settings.get('invoice_show_discount', True):
            headers.append('Discount')
        if settings.get('invoice_show_vat_breakdown', True):
            headers.extend(['VAT Rate', 'VAT Amount'])
        headers.append('Total')

        if template_columns and len(template_columns) == len(headers):
            headers = [col.get('name') or default for col, default in zip(template_columns, headers)]
        items_data = [headers]

        # Add items with product information
        for item in invoice.invoice_items:
            currency_symbol = settings.get('currency', 'BWP')

            # Build product/description column
            product_desc = ''
            if item.product:
                product_desc = f"<b>{item.product.name}</b>"
                if item.description:
                    product_desc += f"<br/><i>{item.description}</i>"
            else:
                product_desc = item.description or 'No description'

            # Build row data
            row_data = [
                product_desc,
                str(item.quantity or 0),
                f"{currency_symbol}{float(item.price or 0):.2f}"
            ]

            # Add discount if enabled
            if settings.get('invoice_show_discount', True):
                discount_pct = float(item.discount_percentage or 0)
                row_data.append(f"{discount_pct:.1f}%")

            # Add VAT columns if enabled
            if settings.get('invoice_show_vat_breakdown', True):
                vat_rate = float(item.vat_rate or 0)
                vat_amount = float(item.vat_amount or 0)
                row_data.extend([f"{vat_rate:.1f}%", f"{currency_symbol}{vat_amount:.2f}"])

            # Add total
            row_data.append(f"{currency_symbol}{float(item.total or 0):.2f}")

            items_data.append(row_data)

        # Create items table with dynamic column widths - much wider for readability
        num_cols = len(headers)
        col_widths: List[float]
        if template_columns and len(template_columns) == num_cols:
            # Convert mm widths to ReportLab units
            col_widths = [self._safe_float(col.get('width'), 0) * mm if col.get('width') else 0 for col in template_columns]
            # Fallback to heuristic widths if any width is zero
            if not all(w > 0 for w in col_widths):
                template_columns = []  # force fallback

        if not template_columns:
            if num_cols == 4:
                col_widths = [100*mm, 18*mm, 28*mm, 29*mm]
            elif num_cols == 5:
                col_widths = [85*mm, 15*mm, 22*mm, 22*mm, 21*mm]
            elif num_cols == 6:
                col_widths = [75*mm, 14*mm, 18*mm, 18*mm, 18*mm, 22*mm]
            elif num_cols == 7:
                col_widths = [68*mm, 13*mm, 16*mm, 14*mm, 14*mm, 18*mm, 22*mm]
            else:
                col_widths = [170*mm // num_cols] * num_cols

        if sum(col_widths) > doc.width:
            scale = doc.width / sum(col_widths)
            col_widths = [w * scale for w in col_widths]

        # Get table styling from settings
        table_header_bg = colors.HexColor(settings.get('items_table_header_bg', '#343a40'))
        table_header_text = colors.HexColor(settings.get('items_table_header_text', '#ffffff'))
        table_border_color = colors.HexColor(settings.get('items_table_border_color', '#dee2e6'))
        table_stripe_color = colors.HexColor(settings.get('items_table_stripe_color', '#f8f9fa'))
        table_font_size = self._safe_int(settings.get('items_table_font_size', 12), 12)

        items_table = Table(items_data, colWidths=col_widths)
        items_table.setStyle(TableStyle([
            # Header styling with customizable colors
            ('BACKGROUND', (0, 0), (-1, 0), table_header_bg),
            ('TEXTCOLOR', (0, 0), (-1, 0), table_header_text),
            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
            ('FONTNAME', (0, 0), (-1, 0), f'{font_family}-Bold'),
            ('FONTSIZE', (0, 0), (-1, 0), table_font_size),

            # Data styling
            ('FONTNAME', (0, 1), (-1, -1), font_family),
            ('FONTSIZE', (0, 1), (-1, -1), table_font_size - 1),
            ('ALIGN', (1, 1), (-1, -1), 'RIGHT'),  # Align numbers right
            ('ALIGN', (0, 1), (0, -1), 'LEFT'),    # Align description left

            # Borders with customizable color
            ('GRID', (0, 0), (-1, -1), 1, table_border_color),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),

            # Alternating row colors
            ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, table_stripe_color]),

            # Allow long text (e.g., product descriptions) to wrap instead of overlapping
            ('WORDWRAP', (0, 0), (-1, -1), 'CJK'),
        ]))

        story.append(items_table)
        story.append(Spacer(1, 20))

        currency_symbol = settings.get('currency', 'BWP')

        if settings.get('invoice_show_totals', True):
            subtotal = float(invoice.total_amount or 0) - float(invoice.total_vat_amount or 0)
            totals_data = []

            if settings.get('invoice_show_subtotal', True):
                totals_data.append(['Subtotal:', f"{currency_symbol}{subtotal:.2f}"])

            if (settings.get('invoice_show_discount', True) and float(invoice.discount_amount or 0) > 0):
                totals_data.append(['Discount:', f"{currency_symbol}{float(invoice.discount_amount or 0):.2f}"])

            if settings.get('invoice_show_vat_breakdown', True):
                totals_data.append(['VAT Total:', f"{currency_symbol}{float(invoice.total_vat_amount or 0):.2f}"])

            if settings.get('invoice_show_total_amount', True):
                totals_data.append(['Total Amount:', f"{currency_symbol}{float(invoice.total_amount or 0):.2f}"])

            totals_bg = colors.HexColor(settings.get('totals_section_background', '#f8f9fa'))
            totals_font_size = self._safe_int(settings.get('totals_font_size', 14), 14)
            totals_font_weight = settings.get('totals_font_weight', 'normal')
            totals_font = f"{font_family}-Bold" if totals_font_weight == 'bold' else font_family

            totals_table = Table(totals_data, colWidths=[40*mm, 30*mm], hAlign='RIGHT')
            totals_table.setStyle(TableStyle([
                ('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
                ('FONTNAME', (0, 0), (0, -1), f'{font_family}-Bold'),
                ('FONTNAME', (1, 0), (1, -1), font_family),
                ('FONTNAME', (0, -1), (-1, -1), f'{font_family}-Bold'),
                ('FONTSIZE', (0, 0), (-1, -1), totals_font_size),
                ('FONTSIZE', (0, -1), (-1, -1), totals_font_size + 2),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
                ('TOPPADDING', (0, 0), (-1, -1), 8),
                ('LINEBELOW', (0, -2), (-1, -2), 1, colors.black),
                ('BACKGROUND', (0, -1), (-1, -1), totals_bg),
            ]))

            story.append(totals_table)
            story.append(Spacer(1, 30))

        # Payment terms and notes
        if invoice.notes:
            story.append(Paragraph("Notes:", header_style))
            story.append(Paragraph(invoice.notes, styles['Normal']))
            story.append(Spacer(1, 12))

        if settings.get('invoice_show_footer', True):
            footer_style = ParagraphStyle(
                'FooterStyle',
                parent=styles['Normal'],
                fontSize=self._safe_int(settings.get('invoice_footer_font_size', 11), 11),
                fontName=font_family,
                textColor=colors.HexColor(settings.get('invoice_footer_text_color', '#000000')),
                alignment=getattr(TA_CENTER if settings.get('invoice_footer_alignment') == 'center'
                               else TA_RIGHT if settings.get('invoice_footer_alignment') == 'right'
                               else TA_LEFT, 'value', TA_LEFT)
            )

            footer_parts = []

            if settings.get('invoice_show_footer_text', True):
                footer_text = settings.get('invoice_footer_text', '')
                if footer_text:
                    footer_parts.append(footer_text)

            if settings.get('invoice_show_terms_conditions', True):
                terms_conditions = settings.get('invoice_terms_conditions', '')
                if terms_conditions:
                    footer_parts.append(f"<b>Terms & Conditions:</b> {terms_conditions}")

            if settings.get('invoice_show_bank_details', False):
                bank_details = settings.get('bank_details', '')
                if bank_details:
                    footer_parts.append(f"<b>Bank Details:</b> {bank_details}")

            footer_parts.append("<b>Thank you for your business!</b>")
            footer_parts.append("This is a computer-generated invoice and does not require a signature.")

            footer_text_final = "<br/><br/>".join(footer_parts)
            story.append(Paragraph(footer_text_final, footer_style))

        # Build PDF
        doc.build(story)
        buffer.seek(0)

        return buffer

    def get_invoice_list(
        self,
        branch_id: str = None,
        customer_id: str = None,
        status: str = None,
        date_from: date = None,
        date_to: date = None,
        limit: int = 100,
        offset: int = 0
    ) -> List[Invoice]:
        """Get filtered list of invoices"""
        from sqlalchemy.orm import joinedload

        query = self.db.query(Invoice).options(joinedload(Invoice.customer))

        if branch_id:
            query = query.filter(Invoice.branch_id == branch_id)
        if customer_id:
            query = query.filter(Invoice.customer_id == customer_id)
        if status:
            if status == 'outstanding':
                # Outstanding means invoices that are not fully paid and not draft or cancelled
                query = query.filter(
                    Invoice.status.notin_(['draft', 'cancelled']),
                    Invoice.amount_paid < Invoice.total_amount
                )
            else:
                query = query.filter(Invoice.status == status)
        if date_from:
            query = query.filter(Invoice.date >= date_from)
        if date_to:
            query = query.filter(Invoice.date <= date_to)

        return query.order_by(desc(Invoice.created_at)).offset(offset).limit(limit).all()

    def mark_invoice_sent(self, invoice_id: str, method: str = 'email'):
        """Mark invoice as sent via email/WhatsApp"""
        invoice = self.db.query(Invoice).filter(Invoice.id == invoice_id).first()
        if invoice:
            invoice.status = 'sent'
            if method == 'whatsapp':
                invoice.sent_via_whatsapp = True
                invoice.whatsapp_sent_at = datetime.now()
            else:
                invoice.sent_via_email = True
                invoice.email_sent_at = datetime.now()
            self.db.commit()

    def mark_invoice_paid(self, invoice_id: str, payment_amount: float, payment_date: date = None):
        """Mark invoice as paid and create accounting entries"""
        invoice = self.db.query(Invoice).filter(Invoice.id == invoice_id).first()
        if not invoice:
            raise ValueError("Invoice not found")

        payment_amount = Decimal(str(payment_amount))
        invoice.amount_paid += payment_amount
        invoice.amount_due = invoice.total_amount - invoice.amount_paid

        if invoice.amount_due <= 0:
            invoice.status = 'paid'
            invoice.paid_at = datetime.now()
        else:
            invoice.status = 'partial'

        # Create payment record for accounting service
        from app.models.sales import Payment
        import uuid

        payment_record = Payment(
            id=str(uuid.uuid4()),
            invoice_id=invoice.id,
            customer_id=invoice.customer_id,
            amount=payment_amount,
            payment_date=payment_date or date.today(),
            payment_method='cash',  # Default for manual payments
            payment_status="completed"
        )

        # Use the standardized AccountingService for payment recording
        try:
            from app.services.accounting_service import AccountingService
            accounting_service = AccountingService(self.db)
            success = accounting_service.record_payment(payment_record)
            if not success:
                print(f"[PAYMENT_WARN] Accounting service failed to record payment for invoice {invoice.invoice_number}")
        except Exception as e:
            print(f"[PAYMENT_ERROR] Error recording payment accounting: {str(e)}")

        self.db.commit()

def get_invoice_service(db: Session = None) -> InvoiceService:
    """Factory function to get InvoiceService instance"""
    if db is None:
        db = next(get_db())
    return InvoiceService(db)
