"""
Authorization certificate helper for HWINV-205.

Provides a deterministic SHA-256 hash for the certificate and a PDF generator
backed by the same WeasyPrint + Jinja2 pipeline used for invoices.
"""

import hashlib
import ipaddress
import logging
import socket
from datetime import datetime
from pathlib import Path
from typing import Any, Optional
from urllib.parse import urlparse

import jinja2
import jinja2.sandbox
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)

_PRIVATE_NETWORKS = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("169.254.0.0/16"),
    ipaddress.ip_network("::1/128"),
    ipaddress.ip_network("fc00::/7"),
]


def _safe_url_fetcher(url: str):
    """Custom WeasyPrint URL fetcher that blocks SSRF vectors."""
    parsed = urlparse(url)
    if parsed.scheme not in ("https",):
        raise ValueError(f"Blocked non-HTTPS URL in PDF template: {parsed.scheme}://")
    try:
        ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
        for network in _PRIVATE_NETWORKS:
            if ip in network:
                raise ValueError(f"Blocked internal IP in PDF template URL: {ip}")
    except socket.gaierror:
        pass  # hostname not resolvable — let WeasyPrint handle the error
    from weasyprint.urls import default_url_fetcher as _default_fetcher
    return _default_fetcher(url)

TEMPLATES_DIR = Path(__file__).parent.parent / "templates"


def generate_cert_hash(
    invoice_literal: str,
    authorized_at: datetime,
    authorization_type: str,
    merchant_id: int,
) -> str:
    """
    Produce a deterministic SHA-256 fingerprint for an authorization certificate.

    Formula:
        SHA-256( invoice_literal + authorized_at.isoformat() + authorization_type + str(merchant_id) )

    Args:
        invoice_literal:    e.g. "INV000042"
        authorized_at:      UTC datetime of authorization (must be timezone-aware)
        authorization_type: e.g. "CHECKBOX", "SIGN", "SMS"
        merchant_id:        integer PK of the merchant

    Returns:
        64-character hex string.
    """
    data = (
        f"{invoice_literal}"
        f"{authorized_at.isoformat()}"
        f"{authorization_type}"
        f"{str(merchant_id)}"
    )
    return hashlib.sha256(data.encode("utf-8")).hexdigest()


class AuthCertificatePDFGenerator:
    """Render an authorization certificate PDF for a given invoice + authorization."""

    def __init__(self) -> None:
        self._env = jinja2.sandbox.SandboxedEnvironment(
            loader=jinja2.FileSystemLoader(str(TEMPLATES_DIR)),
            autoescape=True,
        )

    def generate_pdf(
        self,
        invoice: Any,
        merchant: Any,
        customer: Any,
        authorization: Any,
        cert_hash: str,
        db: Optional[Session] = None,
    ) -> bytes:
        """
        Render authorization_certificate.html and return PDF bytes.

        Raises:
            ImportError:    If WeasyPrint is not installed.
            RuntimeError:   If rendering fails.
        """
        try:
            import weasyprint  # noqa: F401 lazy import
        except ImportError as exc:
            raise ImportError(
                "WeasyPrint is required for PDF generation. "
                "Install with: pip install weasyprint"
            ) from exc

        html_content: Optional[str] = None

        if db is not None:
            try:
                from src.apps.site_templates import crud as site_template_crud

                tmpl = site_template_crud.get_template_by_key(
                    db, "authorization_certificate_pdf"
                )
                if tmpl and tmpl.is_active and tmpl.body_html:
                    db_ctx = self._build_db_context(
                        invoice=invoice,
                        merchant=merchant,
                        customer=customer,
                        authorization=authorization,
                        cert_hash=cert_hash,
                    )
                    sb_env = jinja2.sandbox.SandboxedEnvironment(
                        loader=jinja2.BaseLoader()
                    )
                    html_content = sb_env.from_string(tmpl.body_html).render(**db_ctx)
            except Exception as exc:
                logger.warning(
                    "DB-first template rendering failed for authorization_certificate_pdf, "
                    "falling back to disk: %s",
                    exc,
                )
                html_content = None

        if html_content is None:
            template = self._env.get_template("authorization_certificate.html")
            html_content = template.render(
                invoice=invoice,
                merchant=merchant,
                customer=customer,
                authorization=authorization,
                cert_hash=cert_hash,
            )

        try:
            return weasyprint.HTML(string=html_content, url_fetcher=_safe_url_fetcher).write_pdf()
        except Exception as exc:
            logger.error("Auth certificate PDF generation failed: %s", exc, exc_info=True)
            raise RuntimeError(f"Auth certificate PDF generation failed: {exc}") from exc

    def _build_db_context(
        self,
        invoice: Any,
        merchant: Any,
        customer: Any,
        authorization: Any,
        cert_hash: str = "",
    ) -> dict:
        merchant_name = getattr(merchant, "name", "") or ""
        logo_url = getattr(merchant, "logo_url", "") or ""

        first = getattr(customer, "first_name", "") or ""
        last = getattr(customer, "last_name", "") or ""
        customer_name = f"{first} {last}".strip() or getattr(customer, "business_legal_name", "") or ""

        auth_type = getattr(authorization, "authorization_type", "") or ""
        signed_at = getattr(authorization, "authorized_at", None)
        if signed_at is not None and not isinstance(signed_at, str):
            try:
                signed_at = signed_at.isoformat()
            except Exception:
                signed_at = str(signed_at)

        ip_address = getattr(authorization, "ip_address", "") or ""
        signature_url = getattr(authorization, "signature_url", "") or ""

        return {
            "merchant_name": merchant_name,
            "logo_url": logo_url,
            "customer_name": customer_name,
            "auth_type": auth_type,
            "signed_at": signed_at or "",
            "ip_address": ip_address,
            "signature_url": signature_url,
            "cert_hash": cert_hash,
        }
