package routes

import (
	"bssapp-backend/queries"
	"bytes"
	"database/sql"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/jung-kurt/gofpdf"
)

type orderListPDFRow struct {
	OrderNumber        string
	OrderDate          string
	TerminTarihi       string
	CurrAccCode        string
	CurrAccDescription string
	MusteriTemsilcisi  string
	Piyasa             string
	DocCurrencyCode    string
	TotalAmount        float64
	TotalAmountUSD     float64
	PackedUSD          float64
	PackedRatePct      float64
	HasUretimUrunu     bool
	Description        string
}

type pdfColumn struct {
	Header   string
	Width    float64
	Align    string
	Wrap     bool
	MaxLines int
}

func OrderListPDFRoute(db *sql.DB) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		search := strings.TrimSpace(r.URL.Query().Get("search"))
		currAcc := strings.TrimSpace(r.URL.Query().Get("CurrAccCode"))
		orderDate := strings.TrimSpace(r.URL.Query().Get("OrderDate"))
		sortBy := strings.TrimSpace(r.URL.Query().Get("sort_by"))
		descending := parseBool(r.URL.Query().Get("descending"))

		rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate)
		if err != nil {
			http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		out := make([]orderListPDFRow, 0, 256)
		for rows.Next() {
			var (
				id, no, date, termin, code, name string
				rep, piyasa, cur                 string
				total                            float64
				totalUSD                         float64
				packedAmount                     float64
				packedUSD                        float64
				packedRatePct                    float64
				hasUretim                        bool
				desc                             string
				usdRate                          float64
			)

			if err := rows.Scan(
				&id, &no, &date, &termin, &code, &name, &rep, &piyasa, &cur,
				&total, &totalUSD, &packedAmount, &packedUSD, &packedRatePct,
				&hasUretim, &desc, &usdRate,
			); err != nil {
				http.Error(w, "scan error: "+err.Error(), http.StatusInternalServerError)
				return
			}
			_ = id
			_ = packedAmount
			_ = usdRate

			out = append(out, orderListPDFRow{
				OrderNumber:        no,
				OrderDate:          date,
				TerminTarihi:       termin,
				CurrAccCode:        code,
				CurrAccDescription: name,
				MusteriTemsilcisi:  rep,
				Piyasa:             piyasa,
				DocCurrencyCode:    cur,
				TotalAmount:        total,
				TotalAmountUSD:     totalUSD,
				PackedUSD:          packedUSD,
				PackedRatePct:      packedRatePct,
				HasUretimUrunu:     hasUretim,
				Description:        desc,
			})
		}

		applyOrderListSort(out, sortBy, descending)

		pdf := gofpdf.New("L", "mm", "A4", "")
		pdf.SetMargins(8, 8, 8)
		pdf.SetAutoPageBreak(false, 10)
		if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
			http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		drawOrderListPDF(pdf, out, search, currAcc, orderDate, sortBy, descending)
		if err := pdf.Error(); err != nil {
			http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		var buf bytes.Buffer
		if err := pdf.Output(&buf); err != nil {
			http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/pdf")
		w.Header().Set("Content-Disposition", "inline; filename=\"siparis-listesi.pdf\"")
		_, _ = w.Write(buf.Bytes())
	})
}

func drawOrderListPDF(
	pdf *gofpdf.Fpdf,
	rows []orderListPDFRow,
	search, currAcc, orderDate, sortBy string,
	desc bool,
) {
	cols := []pdfColumn{
		{Header: "Siparis No", Width: 20, Align: "L", Wrap: false, MaxLines: 1},
		{Header: "Tarih", Width: 12, Align: "C", Wrap: false, MaxLines: 1},
		{Header: "Termin", Width: 12, Align: "C", Wrap: false, MaxLines: 1},
		{Header: "Cari Kod", Width: 15, Align: "L", Wrap: true, MaxLines: 2},
		{Header: "Cari Adi", Width: 27, Align: "L", Wrap: true, MaxLines: 3},
		{Header: "Temsilci", Width: 18, Align: "L", Wrap: true, MaxLines: 2},
		{Header: "Piyasa", Width: 13, Align: "L", Wrap: true, MaxLines: 2},
		{Header: "PB", Width: 8, Align: "C", Wrap: false, MaxLines: 1},
		{Header: "Tutar", Width: 15, Align: "R", Wrap: false, MaxLines: 1},
		{Header: "USD", Width: 15, Align: "R", Wrap: false, MaxLines: 1},
		{Header: "Paket USD", Width: 15, Align: "R", Wrap: false, MaxLines: 1},
		{Header: "%", Width: 8, Align: "R", Wrap: false, MaxLines: 1},
		{Header: "Uretim", Width: 24, Align: "L", Wrap: true, MaxLines: 3},
		{Header: "Aciklama", Width: 52, Align: "L", Wrap: true, MaxLines: 3},
	}

	pageW, pageH := pdf.GetPageSize()
	marginL, marginT, marginR := 8.0, 8.0, 8.0
	bottomLimit := pageH - 10.0
	lineH := 3.8
	cellPadX := 1.1
	cellPadY := 0.6

	drawHeader := func() {
		pdf.AddPage()
		pdf.SetFont("dejavu", "B", 12)
		pdf.CellFormat(0, 7, "Siparis Listesi", "", 1, "L", false, 0, "")

		pdf.SetFont("dejavu", "", 8)
		sortText := "TotalAmountUSD DESC (varsayilan)"
		if sortBy != "" {
			dir := "ASC"
			if desc {
				dir = "DESC"
			}
			sortText = fmt.Sprintf("%s %s", sortBy, dir)
		}
		filterLine := fmt.Sprintf(
			"Filtreler -> Arama: %s | Cari: %s | Siparis Tarihi: %s | Siralama: %s | Olusturma: %s",
			emptyDash(search),
			emptyDash(currAcc),
			emptyDash(orderDate),
			sortText,
			time.Now().Format("2006-01-02 15:04"),
		)
		pdf.CellFormat(0, 5, filterLine, "", 1, "L", false, 0, "")
		pdf.Ln(1)

		pdf.SetFont("dejavu", "B", 8)
		pdf.SetFillColor(240, 240, 240)
		for _, c := range cols {
			pdf.CellFormat(c.Width, 6, c.Header, "1", 0, "C", true, 0, "")
		}
		pdf.Ln(-1)
		pdf.SetFont("dejavu", "", 7)
	}

	buildCells := func(row orderListPDFRow) []string {
		uretim := ""
		if row.HasUretimUrunu {
			uretim = "Uretime Verilecek Urunu Var"
		}
		return []string{
			row.OrderNumber,
			row.OrderDate,
			row.TerminTarihi,
			row.CurrAccCode,
			row.CurrAccDescription,
			row.MusteriTemsilcisi,
			row.Piyasa,
			row.DocCurrencyCode,
			fmt.Sprintf("%.2f", row.TotalAmount),
			fmt.Sprintf("%.2f", row.TotalAmountUSD),
			fmt.Sprintf("%.2f", row.PackedUSD),
			fmt.Sprintf("%.2f", row.PackedRatePct),
			uretim,
			row.Description,
		}
	}

	calcLineCount := func(col pdfColumn, text string) int {
		if !col.Wrap {
			return 1
		}
		lines := pdf.SplitLines([]byte(strings.TrimSpace(text)), col.Width-(cellPadX*2))
		n := len(lines)
		if n < 1 {
			n = 1
		}
		if col.MaxLines > 0 && n > col.MaxLines {
			n = col.MaxLines
		}
		return n
	}

	drawWrappedCell := func(x, y, w, h float64, text, align string, maxLines int) {
		pdf.Rect(x, y, w, h, "D")
		lines := pdf.SplitLines([]byte(strings.TrimSpace(text)), w-(cellPadX*2))
		if len(lines) == 0 {
			lines = [][]byte{[]byte("")}
		}
		if maxLines > 0 && len(lines) > maxLines {
			lines = lines[:maxLines]
		}
		ty := y + cellPadY + 2.8
		for _, ln := range lines {
			pdf.SetXY(x+cellPadX, ty-2.8)
			pdf.CellFormat(w-(cellPadX*2), 3.8, string(ln), "", 0, align, false, 0, "")
			ty += 3.8
		}
	}

	drawHeader()

	for _, row := range rows {
		cells := buildCells(row)
		maxLines := 1
		for i, c := range cols {
			n := calcLineCount(c, cells[i])
			if n > maxLines {
				maxLines = n
			}
		}
		rowH := (float64(maxLines) * lineH) + (cellPadY * 2)

		if pdf.GetY()+rowH > bottomLimit {
			drawHeader()
		}

		x := marginL
		y := pdf.GetY()
		for i, c := range cols {
			if c.Wrap {
				drawWrappedCell(x, y, c.Width, rowH, cells[i], c.Align, c.MaxLines)
			} else {
				pdf.SetXY(x, y)
				pdf.CellFormat(c.Width, rowH, cells[i], "1", 0, c.Align, false, 0, "")
			}
			x += c.Width
		}
		pdf.SetXY(marginL, y+rowH)
	}

	_ = pageW
	_ = marginT
	_ = marginR
}

func applyOrderListSort(rows []orderListPDFRow, sortBy string, desc bool) {
	if len(rows) <= 1 {
		return
	}

	field := strings.TrimSpace(sortBy)
	if field == "" {
		field = "TotalAmountUSD"
		desc = true
	}

	lessText := func(a, b string) bool {
		aa := strings.ToLower(strings.TrimSpace(a))
		bb := strings.ToLower(strings.TrimSpace(b))
		if desc {
			return aa > bb
		}
		return aa < bb
	}
	lessNum := func(a, b float64) bool {
		if desc {
			return a > b
		}
		return a < b
	}
	lessBool := func(a, b bool) bool {
		av, bv := 0, 0
		if a {
			av = 1
		}
		if b {
			bv = 1
		}
		if desc {
			return av > bv
		}
		return av < bv
	}

	sort.SliceStable(rows, func(i, j int) bool {
		a := rows[i]
		b := rows[j]
		switch field {
		case "OrderNumber":
			return lessText(a.OrderNumber, b.OrderNumber)
		case "OrderDate":
			return lessText(a.OrderDate, b.OrderDate)
		case "TerminTarihi":
			return lessText(a.TerminTarihi, b.TerminTarihi)
		case "CurrAccCode":
			return lessText(a.CurrAccCode, b.CurrAccCode)
		case "CurrAccDescription":
			return lessText(a.CurrAccDescription, b.CurrAccDescription)
		case "MusteriTemsilcisi":
			return lessText(a.MusteriTemsilcisi, b.MusteriTemsilcisi)
		case "Piyasa":
			return lessText(a.Piyasa, b.Piyasa)
		case "DocCurrencyCode":
			return lessText(a.DocCurrencyCode, b.DocCurrencyCode)
		case "TotalAmount":
			return lessNum(a.TotalAmount, b.TotalAmount)
		case "PackedUSD":
			return lessNum(a.PackedUSD, b.PackedUSD)
		case "PackedRatePct":
			return lessNum(a.PackedRatePct, b.PackedRatePct)
		case "HasUretimUrunu":
			return lessBool(a.HasUretimUrunu, b.HasUretimUrunu)
		case "Description":
			return lessText(a.Description, b.Description)
		default:
			return lessNum(a.TotalAmountUSD, b.TotalAmountUSD)
		}
	})
}

func emptyDash(v string) string {
	if strings.TrimSpace(v) == "" {
		return "-"
	}
	return v
}

func parseBool(v string) bool {
	b, err := strconv.ParseBool(strings.TrimSpace(v))
	if err == nil {
		return b
	}
	switch strings.TrimSpace(strings.ToLower(v)) {
	case "1", "yes", "on":
		return true
	default:
		return false
	}
}
