# app/modules/orders/services/invoice_pdf_service.py
"""
Invoice PDF generation service using WeasyPrint.

Renders HTML invoice templates to PDF using Jinja2 + WeasyPrint.
Stores generated PDFs in the configured storage location.
"""

import logging
from datetime import UTC, datetime
from pathlib import Path

from jinja2 import Environment, FileSystemLoader
from sqlalchemy.orm import Session

from app.modules.orders.exceptions import InvoicePDFGenerationException
from app.modules.orders.models.invoice import Invoice

logger = logging.getLogger(__name__)

# Template directory
TEMPLATE_DIR = Path(__file__).parent.parent / "templates" / "invoices"

# PDF storage directory (relative to project root)
PDF_STORAGE_DIR = Path("storage") / "invoices"


class InvoicePDFService:
    """Service for generating invoice PDFs."""

    def __init__(self):
        """Initialize the PDF service with Jinja2 environment."""
        self.env = Environment(
            loader=FileSystemLoader(str(TEMPLATE_DIR)),
            autoescape=True,
        )

    def _ensure_storage_dir(self, store_id: int) -> Path:
        """Ensure the storage directory exists for a store."""
        storage_path = PDF_STORAGE_DIR / str(store_id)
        storage_path.mkdir(parents=True, exist_ok=True)
        return storage_path

    def _get_pdf_filename(self, invoice: Invoice) -> str:
        """Generate PDF filename for an invoice."""
        safe_number = invoice.invoice_number.replace("/", "-").replace("\\", "-")
        return f"{safe_number}.pdf"

    def generate_pdf(
        self,
        db: Session,
        invoice: Invoice,
        force_regenerate: bool = False,
    ) -> str:
        """
        Generate PDF for an invoice.

        Args:
            db: Database session
            invoice: Invoice to generate PDF for
            force_regenerate: If True, regenerate even if PDF already exists

        Returns:
            Path to the generated PDF file
        """
        # Check if PDF already exists
        if invoice.pdf_path and not force_regenerate:
            if Path(invoice.pdf_path).exists():
                logger.debug(f"PDF already exists for invoice {invoice.invoice_number}")
                return invoice.pdf_path

        # Ensure storage directory exists
        storage_dir = self._ensure_storage_dir(invoice.store_id)
        pdf_filename = self._get_pdf_filename(invoice)
        pdf_path = storage_dir / pdf_filename

        # Render HTML template
        html_content = self._render_html(invoice)

        # Generate PDF using WeasyPrint
        try:
            from weasyprint import HTML

            html_doc = HTML(string=html_content, base_url=str(TEMPLATE_DIR))
            html_doc.write_pdf(str(pdf_path))

            logger.info(f"Generated PDF for invoice {invoice.invoice_number} at {pdf_path}")
        except ImportError:
            logger.error("WeasyPrint not installed. Install with: pip install weasyprint")
            raise InvoicePDFGenerationException(
                invoice_id=invoice.id, reason="WeasyPrint not installed"
            )
        except OSError as e:
            logger.error(f"Failed to generate PDF for invoice {invoice.invoice_number}: {e}")
            raise

        # Update invoice record with PDF path and timestamp
        invoice.pdf_path = str(pdf_path)
        invoice.pdf_generated_at = datetime.now(UTC)
        db.flush()

        return str(pdf_path)

    def _render_html(self, invoice: Invoice) -> str:
        """Render the invoice HTML template."""
        template = self.env.get_template("invoice.html")

        context = {
            "invoice": invoice,
            "seller": invoice.seller_details,
            "buyer": invoice.buyer_details,
            "line_items": invoice.line_items,
            "bank_details": invoice.bank_details,
            "payment_terms": invoice.payment_terms,
            "footer_text": invoice.footer_text,
            "now": datetime.now(UTC),
        }

        return template.render(**context)

    def get_pdf_path(self, invoice: Invoice) -> str | None:
        """Get the PDF path for an invoice if it exists."""
        if invoice.pdf_path and Path(invoice.pdf_path).exists():
            return invoice.pdf_path
        return None

    def delete_pdf(self, invoice: Invoice, db: Session) -> bool:
        """Delete the PDF file for an invoice."""
        if not invoice.pdf_path:
            return False

        pdf_path = Path(invoice.pdf_path)
        if pdf_path.exists():
            try:
                pdf_path.unlink()
                logger.info(f"Deleted PDF for invoice {invoice.invoice_number}")
            except OSError as e:
                logger.error(f"Failed to delete PDF {pdf_path}: {e}")
                return False

        invoice.pdf_path = None
        invoice.pdf_generated_at = None
        db.flush()

        return True

    def regenerate_pdf(self, db: Session, invoice: Invoice) -> str:
        """Force regenerate PDF for an invoice."""
        return self.generate_pdf(db, invoice, force_regenerate=True)


# Singleton instance
invoice_pdf_service = InvoicePDFService()
