package utils

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"
)

// ExportHelper provides unified logic for document generation
type ExportHelper struct {
	pdfGen          *PdfGenerator
	settingProvider SettingValueGetter
}

type SettingValueGetter interface {
	Get(key string) (string, error)
}

func NewExportHelper(pdfGen *PdfGenerator, settings ...SettingValueGetter) *ExportHelper {
	helper := &ExportHelper{pdfGen: pdfGen}
	if len(settings) > 0 {
		helper.settingProvider = settings[0]
	}
	return helper
}

func getRepositoryRoot() string {
	_, currentFile, _, ok := runtime.Caller(0)
	if !ok {
		return "."
	}

	return filepath.Clean(filepath.Join(filepath.Dir(currentFile), "..", ".."))
}

// GetLogoBase64 retrieves the corporate logo in base64 format for PDF embedding
func (h *ExportHelper) GetLogoBase64() string {
	if brandingLogo := strings.TrimSpace(h.getSettingValue("branding_logo_base64")); brandingLogo != "" {
		if strings.HasPrefix(brandingLogo, "data:image/") || strings.HasPrefix(brandingLogo, "http://") || strings.HasPrefix(brandingLogo, "https://") || strings.HasPrefix(brandingLogo, "/") {
			return brandingLogo
		}

		return "data:image/png;base64," + brandingLogo
	}

	logoPath := filepath.Join(getRepositoryRoot(), "public", "assets", "img", "altrak-logo.png")
	logoData, err := os.ReadFile(logoPath)
	if err != nil {
		return ""
	}
	return "data:image/png;base64," + base64.StdEncoding.EncodeToString(logoData)
}

func (h *ExportHelper) getSettingValue(key string) string {
	if h == nil || h.settingProvider == nil {
		return ""
	}

	value, err := h.settingProvider.Get(key)
	if err != nil {
		return ""
	}

	return strings.TrimSpace(value)
}

func (h *ExportHelper) getSettingValueOrDefault(key, fallback string) string {
	if value := h.getSettingValue(key); value != "" {
		return value
	}

	return fallback
}

// GenerateStandardPDF executes a template with partials and returns generated bytes
func (h *ExportHelper) GenerateStandardPDF(templateName string, data map[string]interface{}) ([]byte, error) {
	// Inject common assets if missing
	if _, ok := data["LogoBase64"]; !ok {
		data["LogoBase64"] = h.GetLogoBase64()
	}
	if _, ok := data["CompanyName"]; !ok {
		data["CompanyName"] = h.getSettingValueOrDefault("company_name", "PT. ALTRAK 1978")
	}
	if _, ok := data["BranchName"]; !ok {
		data["BranchName"] = h.getSettingValueOrDefault("branch_name", "Banjarmasin")
	}
	if _, ok := data["DocumentHeaderTitle"]; !ok {
		data["DocumentHeaderTitle"] = h.getSettingValueOrDefault("document_header_title", "PT. ALTRAK 1978 - BANJARMASIN")
	}
	if header, ok := data["Header"].(map[string]interface{}); ok {
		if _, exists := header["CompanyName"]; !exists {
			header["CompanyName"] = data["CompanyName"]
		}
		if _, exists := header["BranchName"]; !exists {
			header["BranchName"] = data["BranchName"]
		}
		if _, exists := header["DocumentHeaderTitle"]; !exists {
			header["DocumentHeaderTitle"] = data["DocumentHeaderTitle"]
		}
	}

	funcMap := h.getTemplateFunctions()
	rootDir := getRepositoryRoot()
	templatePath := filepath.Join(rootDir, "resources", "views", filepath.FromSlash(templateName))
	entryTemplate := filepath.Base(templateName)

	tmpl, err := template.New(entryTemplate).Funcs(funcMap).ParseFiles(
		templatePath,
		filepath.Join(rootDir, "resources", "views", "partials", "pdf_header.html"),
	)
	if err != nil {
		return nil, fmt.Errorf("failed to load blueprint protocol [%s]: %v", templateName, err)
	}

	var rendered bytes.Buffer
	if err := tmpl.ExecuteTemplate(&rendered, entryTemplate, data); err != nil {
		return nil, fmt.Errorf("failed to execute rendering sequence: %v", err)
	}

	return h.pdfGen.GenerateFromHtml(rendered.String())
}

// getTemplateFunctions returns all available template functions
func (h *ExportHelper) getTemplateFunctions() template.FuncMap {
	return template.FuncMap{
		// Range and iteration functions
		"until": func(start, end int) []int {
			var res []int
			for i := start; i < end; i++ {
				res = append(res, i)
			}
			return res
		},
		"range": func(n int) []int {
			var res []int
			for i := 0; i < n; i++ {
				res = append(res, i)
			}
			return res
		},

		// Math functions
		"add": func(a, b int) int {
			return a + b
		},
		"subtract": func(a, b int) int {
			return a - b
		},
		"multiply": func(a, b int) int {
			return a * b
		},
		"divide": func(a, b int) int {
			if b == 0 {
				return 0
			}
			return a / b
		},
		"inc": func(i int) int {
			return i + 1
		},
		"dec": func(i int) int {
			return i - 1
		},

		// Date formatting functions
		"formatDate": func(t interface{}) string {
			var date time.Time
			switch v := t.(type) {
			case time.Time:
				date = v
			case *time.Time:
				if v == nil {
					return "-"
				}
				date = *v
			default:
				return "-"
			}
			return date.Format("02/01/2006")
		},
		"formatDateTime": func(t interface{}) string {
			var date time.Time
			switch v := t.(type) {
			case time.Time:
				date = v
			case *time.Time:
				if v == nil {
					return "-"
				}
				date = *v
			default:
				return "-"
			}
			return date.Format("02/01/2006 15:04")
		},
		"formatDateLong": func(t interface{}) string {
			var date time.Time
			switch v := t.(type) {
			case time.Time:
				date = v
			case *time.Time:
				if v == nil {
					return "-"
				}
				date = *v
			default:
				return "-"
			}
			return date.Format("Monday, 02 January 2006")
		},
		"formatDateIndonesian": func(t interface{}) string {
			var date time.Time
			switch v := t.(type) {
			case time.Time:
				date = v
			case *time.Time:
				if v == nil {
					return "-"
				}
				date = *v
			default:
				return "-"
			}

			months := []string{
				"", "Januari", "Februari", "Maret", "April", "Mei", "Juni",
				"Juli", "Agustus", "September", "Oktober", "November", "Desember",
			}

			days := []string{
				"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu",
			}

			return fmt.Sprintf("%s, %d %s %d",
				days[date.Weekday()],
				date.Day(),
				months[date.Month()],
				date.Year())
		},

		// Number formatting functions
		"formatCurrency": func(amount interface{}) string {
			var f float64
			switch v := amount.(type) {
			case float64:
				f = v
			case float32:
				f = float64(v)
			case int:
				f = float64(v)
			case int64:
				f = float64(v)
			case string:
				parsed, err := strconv.ParseFloat(v, 64)
				if err != nil {
					return "0.00"
				}
				f = parsed
			default:
				return "0.00"
			}

			s := fmt.Sprintf("%.2f", f)
			parts := strings.Split(s, ".")
			intPart := parts[0]
			decPart := parts[1]

			// Add thousand separators
			for i := len(intPart) - 3; i > 0; i -= 3 {
				intPart = intPart[:i] + "," + intPart[i:]
			}

			return intPart + "." + decPart
		},
		"formatIDR": func(amount interface{}) string {
			formatted := h.getTemplateFunctions()["formatNumber"].(func(interface{}) string)(amount)
			return strings.ReplaceAll(formatted, ",", ".")
		},
		"formatNumber": func(n interface{}) string {
			var num int64
			switch v := n.(type) {
			case int:
				num = int64(v)
			case int64:
				num = v
			case float64:
				num = int64(v)
			case string:
				parsed, err := strconv.ParseInt(v, 10, 64)
				if err != nil {
					return "0"
				}
				num = parsed
			default:
				return "0"
			}

			s := fmt.Sprintf("%d", num)
			for i := len(s) - 3; i > 0; i -= 3 {
				s = s[:i] + "," + s[i:]
			}
			return s
		},
		"formatPercent": func(n interface{}) string {
			var f float64
			switch v := n.(type) {
			case float64:
				f = v
			case float32:
				f = float64(v)
			case int:
				f = float64(v)
			case string:
				parsed, err := strconv.ParseFloat(v, 64)
				if err != nil {
					return "0%"
				}
				f = parsed
			default:
				return "0%"
			}
			return fmt.Sprintf("%.1f%%", f)
		},

		// String functions
		"upper": strings.ToUpper,
		"lower": strings.ToLower,
		"title": strings.Title,
		"trim":  strings.TrimSpace,
		"truncate": func(s string, length int) string {
			if len(s) <= length {
				return s
			}
			return s[:length] + "..."
		},
		"replace": func(old, new, s string) string {
			return strings.ReplaceAll(s, old, new)
		},

		// Conditional functions
		"default": func(defaultValue, value interface{}) interface{} {
			if value == nil || value == "" {
				return defaultValue
			}
			return value
		},
		"empty": func(value interface{}) bool {
			if value == nil {
				return true
			}
			switch v := value.(type) {
			case string:
				return v == ""
			case []interface{}:
				return len(v) == 0
			case map[string]interface{}:
				return len(v) == 0
			default:
				return false
			}
		},
		"notEmpty": func(value interface{}) bool {
			return !h.getTemplateFunctions()["empty"].(func(interface{}) bool)(value)
		},

		// Array/slice functions
		"first": func(slice []interface{}) interface{} {
			if len(slice) == 0 {
				return nil
			}
			return slice[0]
		},
		"last": func(slice []interface{}) interface{} {
			if len(slice) == 0 {
				return nil
			}
			return slice[len(slice)-1]
		},
		"length": func(slice []interface{}) int {
			return len(slice)
		},

		// Roman numeral function
		"roman": func(month int) string {
			return GetRomanMonth(month)
		},

		// Indonesian currency
		"rupiah": func(amount interface{}) string {
			formatted := h.getTemplateFunctions()["formatCurrency"].(func(interface{}) string)(amount)
			return "Rp " + formatted
		},

		// Safe division
		"safeDiv": func(a, b float64) float64 {
			if b == 0 {
				return 0
			}
			return a / b
		},

		// HTML safe functions
		"htmlSafe": func(s string) template.HTML {
			return template.HTML(s)
		},
		"urlSafe": func(s string) template.URL {
			return template.URL(s)
		},

		// Comparison functions
		"eq": func(a, b interface{}) bool {
			return a == b
		},
		"ne": func(a, b interface{}) bool {
			return a != b
		},
		"lt": func(a, b int) bool {
			return a < b
		},
		"le": func(a, b int) bool {
			return a <= b
		},
		"gt": func(a, b int) bool {
			return a > b
		},
		"ge": func(a, b int) bool {
			return a >= b
		},
	}
}
