// Report endpoint — serves HTML and PDF reports.
// Mirrors: odoo/addons/web/controllers/report.py ReportController
package server

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"

	"odoo-go/pkg/orm"
)

// handleReport serves HTML reports.
// Route: /report/html/<report_name>/<ids>
// Mirrors: odoo/addons/web/controllers/report.py report_routes()
func (s *Server) handleReport(w http.ResponseWriter, r *http.Request) {
	// Parse URL: /report/html/account.report_invoice/1,2,3
	parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")
	// Expected: ["report", "html", "<report_name>", "<ids>"]
	if len(parts) < 4 {
		http.Error(w, "Invalid report URL. Expected: /report/html/<report_name>/<ids>", http.StatusBadRequest)
		return
	}

	reportName := parts[2]
	idsStr := parts[3]

	// Parse record IDs
	var ids []int64
	for _, s := range strings.Split(idsStr, ",") {
		s = strings.TrimSpace(s)
		if s == "" {
			continue
		}
		id, err := strconv.ParseInt(s, 10, 64)
		if err != nil {
			http.Error(w, fmt.Sprintf("Invalid ID %q: %v", s, err), http.StatusBadRequest)
			return
		}
		ids = append(ids, id)
	}

	if len(ids) == 0 {
		http.Error(w, "No record IDs provided", http.StatusBadRequest)
		return
	}

	// Get session
	uid := int64(1)
	companyID := int64(1)
	if sess := GetSession(r); sess != nil {
		uid = sess.UID
		companyID = sess.CompanyID
	}

	env, err := orm.NewEnvironment(r.Context(), orm.EnvConfig{
		Pool: s.pool, UID: uid, CompanyID: companyID,
	})
	if err != nil {
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}
	defer env.Close()

	// Determine model from report name
	modelName := resolveReportModel(reportName)
	if modelName == "" {
		http.Error(w, "Unknown report: "+reportName, http.StatusNotFound)
		return
	}

	// Read records
	rs := env.Model(modelName).Browse(ids...)
	records, err := rs.Read(nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := env.Commit(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Render HTML report
	html := renderHTMLReport(reportName, modelName, records)
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Write([]byte(html))
}

// handleReportPDF serves PDF reports.
// Route: /report/pdf/<report_name>/<ids>
// Mirrors: odoo/addons/web/controllers/report.py report_download()
//
// It generates the HTML report and converts it to PDF via wkhtmltopdf or
// headless Chromium. If neither is available, it falls back to the HTML
// report with an auto-print dialog.
func (s *Server) handleReportPDF(w http.ResponseWriter, r *http.Request) {
	// Parse URL: /report/pdf/account.report_invoice/1,2,3
	parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")
	// Expected: ["report", "pdf", "<report_name>", "<ids>"]
	if len(parts) < 4 {
		http.Error(w, "Invalid report URL. Expected: /report/pdf/<report_name>/<ids>", http.StatusBadRequest)
		return
	}

	reportName := parts[2]
	idsStr := parts[3]

	// Parse record IDs
	var ids []int64
	for _, s := range strings.Split(idsStr, ",") {
		s = strings.TrimSpace(s)
		if s == "" {
			continue
		}
		id, err := strconv.ParseInt(s, 10, 64)
		if err != nil {
			http.Error(w, fmt.Sprintf("Invalid ID %q: %v", s, err), http.StatusBadRequest)
			return
		}
		ids = append(ids, id)
	}

	if len(ids) == 0 {
		http.Error(w, "No record IDs provided", http.StatusBadRequest)
		return
	}

	// Get session
	uid := int64(1)
	companyID := int64(1)
	if sess := GetSession(r); sess != nil {
		uid = sess.UID
		companyID = sess.CompanyID
	}

	env, err := orm.NewEnvironment(r.Context(), orm.EnvConfig{
		Pool: s.pool, UID: uid, CompanyID: companyID,
	})
	if err != nil {
		http.Error(w, "Internal error", http.StatusInternalServerError)
		return
	}
	defer env.Close()

	// Determine model from report name
	modelName := resolveReportModel(reportName)
	if modelName == "" {
		http.Error(w, "Unknown report: "+reportName, http.StatusNotFound)
		return
	}

	// Read records
	rs := env.Model(modelName).Browse(ids...)
	records, err := rs.Read(nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := env.Commit(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Generate HTML report
	htmlContent := renderHTMLReport(reportName, modelName, records)

	// Try to convert to PDF
	pdfData, err := htmlToPDF(htmlContent)
	if err != nil {
		// Fallback: serve HTML with auto-print dialog
		log.Printf("report: PDF conversion unavailable (%v), falling back to HTML", err)
		htmlFallback := renderHTMLReportPrintFallback(reportName, modelName, records)
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte(htmlFallback))
		return
	}

	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%s.pdf", reportName))
	w.Write(pdfData)
}

// htmlToPDF converts an HTML string to PDF. It tries wkhtmltopdf first,
// then headless Chromium. Returns an error if neither is available.
func htmlToPDF(html string) ([]byte, error) {
	// Try wkhtmltopdf first (most common in Odoo deployments)
	if path, err := exec.LookPath("wkhtmltopdf"); err == nil {
		cmd := exec.Command(path, "--quiet", "--print-media-type",
			"--page-size", "A4",
			"--margin-top", "10mm", "--margin-bottom", "10mm",
			"--margin-left", "10mm", "--margin-right", "10mm",
			"-", "-") // stdin → stdout
		cmd.Stdin = strings.NewReader(html)
		var stdout bytes.Buffer
		var stderr bytes.Buffer
		cmd.Stdout = &stdout
		cmd.Stderr = &stderr

		if err := cmd.Run(); err != nil {
			log.Printf("report: wkhtmltopdf failed: %v – %s", err, stderr.String())
		} else {
			return stdout.Bytes(), nil
		}
	}

	// Try headless Chromium / Chrome
	return htmlToPDFChromium(html)
}

// htmlToPDFChromium converts HTML to PDF using headless Chrome/Chromium.
func htmlToPDFChromium(html string) ([]byte, error) {
	// Find browser binary
	var browserPath string
	for _, name := range []string{"chromium", "chromium-browser", "google-chrome", "chrome"} {
		if p, err := exec.LookPath(name); err == nil {
			browserPath = p
			break
		}
	}
	if browserPath == "" {
		return nil, fmt.Errorf("no PDF converter available (tried wkhtmltopdf, chromium, chrome)")
	}

	// Write HTML to temp file
	tmpFile, err := os.CreateTemp("", "report-*.html")
	if err != nil {
		return nil, fmt.Errorf("creating temp file: %w", err)
	}
	defer os.Remove(tmpFile.Name())

	if _, err := tmpFile.WriteString(html); err != nil {
		tmpFile.Close()
		return nil, fmt.Errorf("writing temp file: %w", err)
	}
	tmpFile.Close()

	pdfFile := tmpFile.Name() + ".pdf"
	defer os.Remove(pdfFile)

	cmd := exec.Command(browserPath, "--headless", "--disable-gpu", "--no-sandbox",
		"--print-to-pdf="+pdfFile, "file://"+tmpFile.Name())
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("chromium PDF generation failed: %v – %s", err, stderr.String())
	}

	data, err := os.ReadFile(pdfFile)
	if err != nil {
		return nil, fmt.Errorf("reading PDF output: %w", err)
	}
	return data, nil
}

// renderHTMLReportPrintFallback generates an HTML report with enhanced print
// CSS and an auto-trigger for the browser print dialog. Used as fallback when
// no PDF converter (wkhtmltopdf, chromium) is installed.
func renderHTMLReportPrintFallback(reportName, modelName string, records []orm.Values) string {
	var b strings.Builder
	b.WriteString(`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Report: `)
	b.WriteString(htmlEscape(reportName))
	b.WriteString(`</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; font-weight: bold; }
h1 { color: #333; }
h2 { color: #555; margin-top: 30px; }
.header { border-bottom: 2px solid #875A7B; padding-bottom: 10px; margin-bottom: 20px; }
.report-info { color: #888; font-size: 0.9em; margin-bottom: 30px; }
.record-section { page-break-after: always; margin-bottom: 40px; }
.record-section:last-child { page-break-after: avoid; }
.no-print { display: none; }
@media print {
  body { margin: 0; }
  .no-print { display: none; }
  table { page-break-inside: auto; }
  tr { page-break-inside: avoid; }
}
@page {
  size: A4;
  margin: 15mm;
}
</style>
</head>
<body>
<div class="header">
  <h1>`)
	b.WriteString(htmlEscape(reportName))
	b.WriteString(`</h1>
  <div class="report-info">Model: `)
	b.WriteString(htmlEscape(modelName))
	b.WriteString(fmt.Sprintf(` | Records: %d`, len(records)))
	b.WriteString(`</div>
</div>
`)

	for _, rec := range records {
		b.WriteString(`<div class="record-section">`)
		title := ""
		if v, ok := rec["display_name"]; ok {
			title = fmt.Sprintf("%v", v)
		} else if v, ok := rec["name"]; ok {
			title = fmt.Sprintf("%v", v)
		}
		if title != "" {
			b.WriteString(fmt.Sprintf("<h2>%s</h2>", htmlEscape(title)))
		}
		b.WriteString("<table>")
		b.WriteString("<tr><th>Field</th><th>Value</th></tr>")
		for key, val := range rec {
			if key == "id" {
				continue
			}
			valStr := fmt.Sprintf("%v", val)
			if valStr == "<nil>" {
				valStr = ""
			}
			b.WriteString(fmt.Sprintf("<tr><td>%s</td><td>%s</td></tr>",
				htmlEscape(key), htmlEscape(valStr)))
		}
		b.WriteString("</table>")
		b.WriteString("</div>")
	}

	b.WriteString(`<script>window.addEventListener('load', function() { window.print(); });</script>
</body></html>`)
	return b.String()
}

// resolveReportModel maps a report name to the ORM model it operates on.
// Mirrors: odoo ir.actions.report → model field.
func resolveReportModel(reportName string) string {
	mapping := map[string]string{
		"account.report_invoice":         "account.move",
		"sale.report_saleorder":          "sale.order",
		"stock.report_picking":           "stock.picking",
		"purchase.report_purchaseorder":  "purchase.order",
		"contacts.report_partner":        "res.partner",
	}
	return mapping[reportName]
}

// renderHTMLReport generates a basic HTML report for the given records.
// This is a minimal implementation — Odoo uses QWeb templates for real reports.
func renderHTMLReport(reportName, modelName string, records []orm.Values) string {
	var b strings.Builder
	b.WriteString(`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Report: `)
	b.WriteString(htmlEscape(reportName))
	b.WriteString(`</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; font-weight: bold; }
h1 { color: #333; }
h2 { color: #555; margin-top: 30px; }
.header { border-bottom: 2px solid #875A7B; padding-bottom: 10px; margin-bottom: 20px; }
.report-info { color: #888; font-size: 0.9em; margin-bottom: 30px; }
.record-section { page-break-after: always; margin-bottom: 40px; }
.record-section:last-child { page-break-after: avoid; }
@media print {
  body { margin: 0; }
  .no-print { display: none; }
  table { page-break-inside: auto; }
  tr { page-break-inside: avoid; }
}
@page {
  size: A4;
  margin: 15mm;
}
</style>
</head>
<body>
<div class="header">
  <h1>`)
	b.WriteString(htmlEscape(reportName))
	b.WriteString(`</h1>
  <div class="report-info">Model: `)
	b.WriteString(htmlEscape(modelName))
	b.WriteString(fmt.Sprintf(` | Records: %d`, len(records)))
	b.WriteString(`</div>
</div>
<div class="no-print">
  <button onclick="window.print()">Print</button>
</div>
`)

	for _, rec := range records {
		b.WriteString(`<div class="record-section">`)

		// Use "name" or "display_name" as section title if available
		title := ""
		if v, ok := rec["display_name"]; ok {
			title = fmt.Sprintf("%v", v)
		} else if v, ok := rec["name"]; ok {
			title = fmt.Sprintf("%v", v)
		}
		if title != "" {
			b.WriteString(fmt.Sprintf("<h2>%s</h2>", htmlEscape(title)))
		}

		b.WriteString("<table>")
		b.WriteString("<tr><th>Field</th><th>Value</th></tr>")
		for key, val := range rec {
			if key == "id" {
				continue
			}
			valStr := fmt.Sprintf("%v", val)
			if valStr == "<nil>" {
				valStr = ""
			}
			b.WriteString(fmt.Sprintf("<tr><td>%s</td><td>%s</td></tr>",
				htmlEscape(key), htmlEscape(valStr)))
		}
		b.WriteString("</table>")
		b.WriteString("</div>")
	}

	b.WriteString("</body></html>")
	return b.String()
}

// htmlEscape escapes HTML special characters.
func htmlEscape(s string) string {
	return template.HTMLEscapeString(s)
}
