package main

import (
	"bssapp-backend/db"
	"bssapp-backend/internal/auditlog"
	"bssapp-backend/internal/mailer"
	"bssapp-backend/middlewares"
	"bssapp-backend/permissions"
	"bssapp-backend/queries"
	"bssapp-backend/repository"
	"bssapp-backend/routes"
	"bssapp-backend/utils"
	"database/sql"
	"log"
	"log/slog"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"strings"

	"github.com/gorilla/mux"
	"github.com/joho/godotenv"
)

/*
===========================================================
✅ CORS
===========================================================
*/
func enableCORS(h http.Handler) http.Handler {

	frontendURL := os.Getenv("APP_FRONTEND_URL")

	// Default fallback (dev için)
	if frontendURL == "" {
		frontendURL = "http://localhost:9000"
	}

	log.Println("🌍 CORS Allowed Origin:", frontendURL)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		origin := r.Header.Get("Origin")

		// Sadece izin verilen origin'e cevap ver
		if origin == frontendURL {
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Vary", "Origin")
			w.Header().Set("Access-Control-Allow-Credentials", "true")
		}

		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")

		// Preflight
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}

		h.ServeHTTP(w, r)
	})
}

/*
===========================================================
✅ V3 — Method-aware Route Auto Register
- mk_sys_routes: (path, method, module_code, action)
- unique: (path, method)
- admin auto-allow: mk_sys_role_permissions (role_id=3)
===========================================================
*/
func autoRegisterRouteV3(
	pg *sql.DB,
	path string,
	method string,
	module string,
	action string,
) {
	tx, err := pg.Begin()
	if err != nil {
		log.Println("❌ TX begin error:", err)
		return
	}
	defer tx.Rollback()

	// 1) ROUTE REGISTER (path+method)
	_, err = tx.Exec(`
		INSERT INTO mk_sys_routes
			(path, method, module_code, action)
		VALUES
			($1, $2, $3, $4)
		ON CONFLICT (path, method) DO UPDATE
		SET
			module_code = EXCLUDED.module_code,
			action      = EXCLUDED.action
	`,
		path,
		method,
		module,
		action,
	)
	if err != nil {
		log.Printf("❌ Route register error (%s %s): %v", method, path, err)
		return
	}

	// 2) MODULE LOOKUP AUTO SEED (permission ekranları için)
	moduleLabel := strings.TrimSpace(strings.ReplaceAll(module, "_", " "))
	switch strings.ToLower(strings.TrimSpace(module)) {
	case "pricing":
		moduleLabel = "Ürün Fiyatlandırma"
	case "costing":
		moduleLabel = "Ürün Maliyetlendirme"
	}
	if moduleLabel == "" {
		moduleLabel = module
	}
	_, err = tx.Exec(`
		INSERT INTO mk_sys_modules (code, name)
		VALUES ($1::text, $2::text)
		ON CONFLICT (code) DO UPDATE
		SET name = COALESCE(NULLIF(EXCLUDED.name, ''), mk_sys_modules.name)
	`,
		module,
		moduleLabel,
	)
	if err != nil {
		log.Printf("❌ Module seed error (%s %s): %v", method, path, err)
		return
	}

	// 3) ROLE PERMISSION AUTO SEED (admin=true, diğer roller=false)
	_, err = tx.Exec(`
		INSERT INTO mk_sys_role_permissions
			(role_id, module_code, action, allowed)
		SELECT
			id,
			$1,
			$2,
			CASE
				WHEN id = 3 OR LOWER(code) = 'admin' THEN true
				ELSE false
			END
		FROM dfrole
		ON CONFLICT DO NOTHING
	`,
		module,
		action,
	)
	if err != nil {
		log.Printf("❌ Role perm seed error (%s %s): %v", method, path, err)
		return
	}

	// 4) ROLE+DEPARTMENT PERMISSION AUTO SEED
	// Existing role+department kombinasyonlarına yeni module+action satırı açılır.
	_, err = tx.Exec(`
		WITH role_dept_scope AS (
			SELECT DISTINCT role_id, department_code
			FROM mk_sys_role_department_permissions
			UNION
			SELECT 3 AS role_id, d.code AS department_code
			FROM mk_dprt d
		)
		INSERT INTO mk_sys_role_department_permissions
			(role_id, department_code, module_code, action, allowed)
		SELECT
			rds.role_id,
			rds.department_code,
			$1,
			$2,
			CASE
				WHEN rds.role_id = 3 THEN true
				ELSE false
			END
		FROM role_dept_scope rds
		ON CONFLICT DO NOTHING
	`,
		module,
		action,
	)
	if err != nil {
		log.Printf("❌ Role+Dept perm seed error (%s %s): %v", method, path, err)
		return
	}

	if err := tx.Commit(); err != nil {
		log.Println("❌ TX commit error:", err)
		return
	}

	log.Printf("✅ Route+Perm registered → %s %s [%s:%s]",
		method, path, module, action,
	)
}

/*
===========================================================
✅ V3 Route Bind Helper
- tek satırda: autoRegister + wrap + Handle
===========================================================
*/
func bindV3(
	r *mux.Router,
	pg *sql.DB,
	path string,
	method string,
	module string,
	action string,
	h http.Handler,
) {
	// main
	autoRegisterRouteV3(pg, path, method, module, action)

	r.Handle(path, h).Methods(method, "OPTIONS")
}

/*
===========================================================
InitRoutes — FULL V3 (Method-aware) PERMISSION EDITION
===========================================================
*/
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {

	r := mux.NewRouter()
	mountUploads(r)
	mountSPA(r)

	/*
		===========================================================
		✅ wrapV3 (method-aware):
		- AuthMiddleware (JWT)
		- panic recover
		- AuthzGuardByRoute(pgDB) => route(path+method) lookup
		===========================================================
	*/
	wrapV3 := func(h http.Handler) http.Handler {
		return middlewares.AuthMiddleware(
			pgDB,
			http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer func() {
					if rec := recover(); rec != nil {
						log.Printf("🔥 PANIC %s %s\n%v", r.Method, r.URL.Path, rec)
						debug.PrintStack()
						http.Error(w, "internal server error", 500)
					}
				}()

				// ✅ method-aware route guard
				middlewares.AuthzGuardByRoute(pgDB)(h).ServeHTTP(w, r)
			}),
		)
	}

	// Sadece JWT doğrulaması; route-level yetki kontrolü yok.
	wrapAuthOnly := func(h http.Handler) http.Handler {
		return middlewares.AuthMiddleware(pgDB, h)
	}

	// ============================================================
	// PUBLIC (NO AUTHZ)
	// ============================================================
	bindV3(r, pgDB,
		"/api/auth/login", "POST",
		"auth", "login",
		http.HandlerFunc(routes.LoginHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/auth/refresh", "POST",
		"auth", "refresh",
		routes.AuthRefreshHandler(pgDB),
	)

	// Password reset flow (public)
	bindV3(r, pgDB,
		"/api/password/forgot", "POST",
		"auth", "update",
		routes.ForgotPasswordHandler(pgDB, ml),
	)
	bindV3(r, pgDB,
		"/api/password/reset/validate/{token}", "GET",
		"auth", "view",
		routes.ValidatePasswordResetTokenHandler(pgDB),
	)
	bindV3(r, pgDB,
		"/api/password/reset", "POST",
		"auth", "update",
		routes.CompletePasswordResetHandler(pgDB),
	)

	// ============================================================
	// SYSTEM
	// ============================================================
	bindV3(r, pgDB,
		"/api/password/change", "POST",
		"auth", "update",
		wrapV3(http.HandlerFunc(routes.FirstPasswordChangeHandler(pgDB))),
	)

	bindV3(r, pgDB,
		"/api/activity-logs", "GET",
		"system", "read",
		wrapV3(routes.AdminActivityLogsHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/test-mail", "POST",
		"system", "update",
		wrapV3(routes.TestMailHandler(ml)),
	)

	bindV3(r, pgDB,
		"/api/system/market-mail-mappings/lookups", "GET",
		"system", "update",
		wrapV3(routes.GetMarketMailMappingLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/market-mail-mappings", "GET",
		"system", "update",
		wrapV3(routes.GetMarketMailMappingsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/market-mail-mappings/{marketId}", "PUT",
		"system", "update",
		wrapV3(routes.SaveMarketMailMappingHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/system/costing-mail-mappings/lookups", "GET",
		"system", "update",
		wrapV3(routes.GetCostingFirstGroupMailMappingLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/costing-mail-mappings", "GET",
		"system", "update",
		wrapV3(routes.GetCostingFirstGroupMailMappingsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/costing-mail-mappings/{group}", "PUT",
		"system", "update",
		wrapV3(routes.SaveCostingFirstGroupMailMappingHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/system/pricing-mail-mappings/lookups", "GET",
		"system", "update",
		wrapV3(routes.GetPricingFirstGroupMailMappingLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/pricing-mail-mappings", "GET",
		"system", "update",
		wrapV3(routes.GetPricingFirstGroupMailMappingsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/pricing-mail-mappings/{group}", "PUT",
		"system", "update",
		wrapV3(routes.SavePricingFirstGroupMailMappingHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/order-price-list-mail-mappings/lookups", "GET",
		"system", "update",
		wrapV3(routes.GetOrderPriceListFirstGroupMailMappingLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/order-price-list-mail-mappings", "GET",
		"system", "update",
		wrapV3(routes.GetOrderPriceListFirstGroupMailMappingsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/order-price-list-mail-mappings/{group}", "PUT",
		"system", "update",
		wrapV3(routes.SaveOrderPriceListFirstGroupMailMappingHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/order-price-list-user-price-groups/lookups", "GET",
		"system", "update",
		wrapV3(routes.GetOrderPriceListPriceGroupLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/order-price-list-user-price-groups", "GET",
		"system", "update",
		wrapV3(routes.GetOrderPriceListUserPriceGroupRowsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/system/order-price-list-user-price-groups/{id}", "PUT",
		"system", "update",
		wrapV3(routes.SaveUserOrderPriceListPriceGroupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/language/translations", "GET",
		"language", "update",
		wrapV3(routes.GetTranslationRowsHandler(pgDB)),
	)
	r.Handle(
		"/api/language/translations/runtime",
		http.HandlerFunc(routes.GetRuntimeTranslationsHandler(pgDB)),
	).Methods("GET", "OPTIONS")
	bindV3(r, pgDB,
		"/api/language/translations/{id}", "PUT",
		"language", "update",
		wrapV3(routes.UpdateTranslationRowHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/language/translations/upsert-missing", "POST",
		"language", "update",
		wrapV3(routes.UpsertMissingTranslationsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/language/translations/sync-sources", "POST",
		"language", "update",
		wrapV3(routes.SyncTranslationSourcesHandler(pgDB, mssql)),
	)
	bindV3(r, pgDB,
		"/api/language/translations/translate-selected", "POST",
		"language", "update",
		wrapV3(routes.TranslateSelectedTranslationsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/language/translations/bulk-approve", "POST",
		"language", "update",
		wrapV3(routes.BulkApproveTranslationsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/language/translations/bulk-update", "POST",
		"language", "update",
		wrapV3(routes.BulkUpdateTranslationsHandler(pgDB)),
	)

	// ============================================================
	// PERMISSIONS
	// ============================================================
	rolePerm := "/api/roles/{id}/permissions"

	bindV3(r, pgDB,
		rolePerm, "GET",
		"system", "update",
		wrapV3(routes.GetRolePermissionMatrix(pgDB)),
	)
	bindV3(r, pgDB,
		rolePerm, "POST",
		"system", "update",
		wrapV3(routes.SaveRolePermissionMatrix(pgDB)),
	)

	userPerm := "/api/users/{id}/permissions"

	bindV3(r, pgDB,
		userPerm, "GET",
		"system", "update",
		wrapV3(routes.GetUserPermissionsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		userPerm, "POST",
		"system", "update",
		wrapV3(routes.SaveUserPermissionsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/users/{id}/order-price-list-price-groups", "GET",
		"user", "update",
		wrapV3(routes.GetUserOrderPriceListPriceGroupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/users/order-price-list-price-groups/lookups", "GET",
		"user", "update",
		wrapV3(routes.GetOrderPriceListPriceGroupLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/users/{id}/order-price-list-price-groups", "PUT",
		"user", "update",
		wrapV3(routes.SaveUserOrderPriceListPriceGroupsHandler(pgDB)),
	)

	// ✅ permissions/routes (system:view)
	bindV3(r, pgDB,
		"/api/permissions/routes", "GET",
		"system", "view",
		wrapV3(routes.GetUserRoutePermissionsHandler(pgDB)),
	)

	// ✅ permissions/effective (system:view)
	bindV3(r, pgDB,
		"/api/permissions/effective", "GET",
		"system", "view",
		wrapV3(routes.GetMyEffectivePermissions(pgDB)),
	)

	// ✅ my permission matrix (system:view)  (Senin tabloda user:view görünüyor; düzeltiyoruz)
	bindV3(r, pgDB,
		"/api/permissions/matrix", "GET",
		"system", "view",
		wrapV3(routes.GetMyPermissionMatrix(pgDB)),
	)

	// ============================================================
	// ROLE + DEPARTMENT PERMISSIONS
	// ============================================================
	rdPerm := "/api/roles/{roleId}/departments/{deptCode}/permissions"
	rdHandler := routes.NewRoleDepartmentPermissionHandler(pgDB)

	bindV3(r, pgDB,
		"/api/role-dept-permissions/list", "GET",
		"system", "update",
		wrapV3(http.HandlerFunc(rdHandler.List)),
	)
	bindV3(r, pgDB,
		rdPerm, "GET",
		"system", "update",
		wrapV3(http.HandlerFunc(rdHandler.Get)),
	)
	bindV3(r, pgDB,
		rdPerm, "POST",
		"system", "update",
		wrapV3(http.HandlerFunc(rdHandler.Save)),
	)
	bindV3(r, pgDB,
		"/api/roles/{roleId}/departments/{deptCode}/members", "GET",
		"system", "update",
		wrapV3(http.HandlerFunc(rdHandler.Members)),
	)
	bindV3(r, pgDB,
		"/api/roles/{roleId}/departments/{deptCode}/members", "POST",
		"system", "update",
		wrapV3(http.HandlerFunc(rdHandler.AddMember)),
	)

	// ============================================================
	// USERS
	// ============================================================
	bindV3(r, pgDB,
		"/api/users/list", "GET",
		"user", "view",
		wrapV3(routes.UserListRoute(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/users", "POST",
		"user", "insert",
		wrapV3(routes.UserCreateRoute(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/users/{id}", "GET",
		"user", "update",
		wrapV3(routes.UserDetailRoute(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/users/{id}", "PUT",
		"user", "update",
		wrapV3(routes.UserDetailRoute(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/users/{id}", "DELETE",
		"user", "delete",
		wrapV3(routes.UserDetailRoute(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/users/{id}/admin-reset-password", "POST",
		"user", "update",
		wrapV3(routes.AdminResetPasswordHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/users/{id}/send-password-mail", "POST",
		"user", "update",
		wrapV3(routes.SendPasswordResetMailHandler(pgDB, ml)),
	)

	// ✅ eski kısayol create endpoint (senin eski main.go’da vardı)
	bindV3(r, pgDB,
		"/api/users/create", "POST",
		"user", "insert",
		wrapV3(routes.UserCreateRoute(pgDB)),
	)

	// ============================================================
	// LOOKUPS
	// ============================================================
	lookups := map[string]http.Handler{
		"/api/lookups/roles":            routes.GetRoleLookupRoute(pgDB),
		"/api/lookups/departments":      routes.GetDepartmentLookupRoute(pgDB),
		"/api/lookups/nebim-users":      routes.GetNebimUserLookupRoute(pgDB),
		"/api/lookups/piyasalar":        routes.GetPiyasaLookupRoute(pgDB),
		"/api/lookups/users-perm":       routes.GetUsersForPermissionSelectRoute(pgDB),
		"/api/lookups/roles-perm":       routes.GetRolesForPermissionSelectRoute(pgDB),
		"/api/lookups/departments-perm": routes.GetDepartmentsForPermissionSelectRoute(pgDB),
		"/api/lookups/modules":          routes.GetModuleLookupRoute(pgDB),
	}

	for path, handler := range lookups {
		bindV3(r, pgDB,
			path, "GET",
			"user", "view",
			wrapV3(handler),
		)
	}

	// ============================================================
	// CUSTOMER
	// ============================================================
	bindV3(r, pgDB,
		"/api/accounts", "GET",
		"customer", "view",
		wrapV3(http.HandlerFunc(routes.GetAccountsHandler)),
	)

	bindV3(r, pgDB,
		"/api/customer-list", "GET",
		"customer", "view",
		wrapV3(http.HandlerFunc(routes.GetCustomerListHandler)),
	)

	// ============================================================
	// FINANCE
	// ============================================================
	bindV3(r, pgDB,
		"/api/today-currency", "GET",
		"finance", "view",
		wrapV3(routes.GetTodayCurrencyV3Handler(mssql)),
	)

	bindV3(r, pgDB,
		"/api/export-pdf", "GET",
		"finance", "export",
		wrapV3(routes.ExportPDFHandler(mssql)),
	)
	bindV3(r, pgDB,
		"/api/export-pdf", "POST",
		"finance", "export",
		wrapV3(routes.ExportPDFHandler(mssql)),
	)

	bindV3(r, pgDB,
		"/api/exportstamentheaderreport-pdf", "GET",
		"finance", "export",
		wrapV3(routes.ExportStatementHeaderReportPDFHandler(mssql)),
	)

	bindV3(r, pgDB,
		"/api/finance/customer-balances", "GET",
		"finance", "view",
		wrapV3(http.HandlerFunc(routes.GetCustomerBalanceListHandler)),
	)

	bindV3(r, pgDB,
		"/api/finance/customer-balances/export-pdf", "GET",
		"finance", "export",
		wrapV3(routes.ExportCustomerBalancePDFHandler(mssql)),
	)

	bindV3(r, pgDB,
		"/api/finance/customer-balances/export-excel", "GET",
		"finance", "export",
		wrapV3(routes.ExportCustomerBalanceExcelHandler(mssql)),
	)

	bindV3(r, pgDB,
		"/api/finance/account-aging-statement", "GET",
		"finance", "view",
		wrapV3(http.HandlerFunc(routes.GetStatementAgingHandler)),
	)
	bindV3(r, pgDB,
		"/api/finance/account-aging-statement/export-pdf", "GET",
		"finance", "export",
		wrapV3(routes.ExportStatementAgingPDFHandler(mssql)),
	)
	bindV3(r, pgDB,
		"/api/finance/account-aging-statement/export-screen-pdf", "GET",
		"finance", "export",
		wrapV3(routes.ExportStatementAgingScreenPDFHandler(mssql)),
	)
	bindV3(r, pgDB,
		"/api/finance/account-aging-statement/export-excel", "GET",
		"finance", "export",
		wrapV3(routes.ExportStatementAgingExcelHandler(mssql)),
	)
	bindV3(r, pgDB,
		"/api/finance/aged-customer-balance-list", "GET",
		"finance", "view",
		wrapV3(http.HandlerFunc(routes.GetAgedCustomerBalanceListHandler)),
	)

	// ============================================================
	// REPORT (STATEMENTS)
	// ============================================================
	bindV3(r, pgDB,
		"/api/statements", "GET",
		"finance", "view",
		wrapV3(http.HandlerFunc(routes.GetStatementHeadersHandler)),
	)

	// ⚠️ Senin handler: GetStatementDetailsHandler vars["accountCode"] bekliyor,
	// route’da {id} var. Burada, DB route’unu ve path’i bozmayalım diye {id}’yi koruyorum,
	// ama handler içinde accountCode := mux.Vars(r)["id"] yapman daha doğru.
	bindV3(r, pgDB,
		"/api/statements/{id}/details", "GET",
		"finance", "view",
		wrapV3(http.HandlerFunc(routes.GetStatementDetailsHandler)),
	)

	// ============================================================
	// ORDER
	// ============================================================
	orderRoutes := []struct {
		Path   string
		Method string
		Action string
		Handle http.Handler
	}{
		{"/api/order/create", "POST", "insert", routes.CreateOrderHandler(pgDB, mssql)},
		{"/api/order/update", "POST", "update", http.HandlerFunc(routes.UpdateOrderHandler)},
		{"/api/order/{id}/bulk-due-date", "POST", "update", routes.BulkUpdateOrderLineDueDateHandler(mssql)},
		{"/api/order/get/{id}", "GET", "view", routes.GetOrderByIDHandler(mssql)},
		{"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)},
		{"/api/orders/production-list", "GET", "update", routes.OrderProductionListRoute(mssql)},
		{"/api/orders/production-items/cditem-lookups", "GET", "view", routes.OrderProductionCdItemLookupsRoute(mssql)},
		{"/api/orders/production-items/{id}", "GET", "view", routes.OrderProductionItemsRoute(mssql)},
		{"/api/orders/production-items/{id}/insert-missing", "POST", "update", routes.OrderProductionInsertMissingRoute(mssql)},
		{"/api/orders/production-items/{id}/validate", "POST", "update", routes.OrderProductionValidateRoute(mssql)},
		{"/api/orders/production-items/{id}/apply", "POST", "update", routes.OrderProductionApplyRoute(mssql, ml)},
		{"/api/orders/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
		{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
		{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
		{"/api/order/check/{id}", "GET", "view", routes.OrderExistsHandler(mssql)},
		{"/api/order/validate", "POST", "insert", routes.ValidateOrderHandler(mssql)},
		{"/api/order/pdf/{id}", "GET", "export", routes.OrderPDFHandler(mssql, pgDB)},
		{"/api/order/send-market-mail", "POST", "read", routes.SendOrderMarketMailHandler(pgDB, mssql, ml)},
		{"/api/order-inventory", "GET", "view", http.HandlerFunc(routes.GetOrderInventoryHandler)},
		{"/api/orderpricelistb2b", "GET", "view", routes.GetOrderPriceListB2BHandler(pgDB, mssql)},
		{"/api/min-price", "GET", "view", routes.GetOrderPriceListB2BHandler(pgDB, mssql)},
	}

	for _, rt := range orderRoutes {
		if rt.Path == "/api/order/send-market-mail" {
			bindV3(r, pgDB,
				rt.Path, rt.Method,
				"order", rt.Action,
				wrapAuthOnly(rt.Handle),
			)
			continue
		}

		bindV3(r, pgDB,
			rt.Path, rt.Method,
			"order", rt.Action,
			wrapV3(rt.Handle),
		)
	}

	// ============================================================
	// PRODUCTS (✅ handler mapping fix)
	// ============================================================
	bindV3(r, pgDB,
		"/api/products", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductListHandler)),
	)

	bindV3(r, pgDB,
		"/api/product-detail", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductDetailHandler)),
	)

	bindV3(r, pgDB,
		"/api/product-cditem", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductCdItemHandler)),
	)

	bindV3(r, pgDB,
		"/api/product-colors", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductColorsHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-newcolors", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductNewColorsHandler)),
	)

	bindV3(r, pgDB,
		"/api/product-colorsize", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductColorSizesHandler)),
	)

	bindV3(r, pgDB,
		"/api/product-secondcolor", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-newsecondcolor", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductNewSecondColorsHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-attributes", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductAttributesHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-item-attributes", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductItemAttributesHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-stock-query", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductStockQueryHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-stock-attribute-options", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductStockAttributeOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-stock-query-by-attributes", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductStockQueryByAttributesHandler)),
	)
	bindV3(r, pgDB,
		"/api/product-images", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductImagesHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/product-images/batch", "POST",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.PostProductImagesBatchHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/product-images/{id}/content", "GET",
		"order", "view",
		http.HandlerFunc(routes.GetProductImageContentHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/products", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductPricingListHandler)),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/options", "GET",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.GetProductPricingFilterOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/my-price-groups", "GET",
		"order", "view",
		wrapV3(routes.GetMyOrderPriceListPriceGroupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/campaigns", "GET",
		"order", "view",
		wrapV3(routes.GetWholesaleCampaignsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/variant-rows", "GET",
		"order", "view",
		wrapV3(routes.GetWholesaleCampaignVariantRowsHandler(pgDB, mssql)),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/export-excel", "POST",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.ExportProductPriceListExcelHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/export-pdf", "POST",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.ExportProductPriceListPDFHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/order/price-list/export-notify", "POST",
		"order", "view",
		wrapV3(http.HandlerFunc(routes.NotifyOrderPriceListExportHandler(pgDB, ml))),
	)
	bindV3(r, pgDB,
		"/api/product-size-match/rules", "GET",
		"order", "view",
		wrapV3(routes.GetProductSizeMatchRulesHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products", "GET",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductPricingListHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/options", "GET",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductPricingFilterOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/export-all", "GET",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.ExportAllProductPricingHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/price-list/export-excel", "POST",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.ExportProductPriceListExcelHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/price-list/export-pdf", "POST",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.ExportProductPriceListPDFHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/calculate-snapshots", "POST",
		"pricing", "update",
		wrapV3(routes.PostProductPricingCalculateSnapshotsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/{code}/price-history", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPricingHistoryHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/{code}/price-history/delete-latest", "POST",
		"pricing", "update",
		wrapV3(routes.PostDeleteLatestProductPriceHistoryHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/{code}/price-history/delete-selected", "POST",
		"pricing", "update",
		wrapV3(routes.PostDeleteSelectedProductPriceHistoryHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/products/save", "POST",
		"pricing", "update",
		wrapV3(routes.PostProductPricingSaveHandler(pgDB, ml)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/export-excel", "POST",
		"pricing", "view",
		wrapV3(routes.ExportProductPerformanceExcelHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/summary", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceSummaryHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/general", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceGeneralHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/orders", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceOrdersHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/orders/groups", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceOrderGroupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/orders/product-customers", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceOrderProductCustomersHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/orders/market-details", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceOrderMarketDetailsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/markets", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceMarketsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/countries", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceCountriesHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/customers", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceCustomersHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/sales-breakdown", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceSalesBreakdownHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/grouped", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceGroupedHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/grouped", "POST",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceGroupedHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/grouped/filter-options", "POST",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceGroupedFilterOptionsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/sales-details", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceSalesDetailsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-performance/stock-sizes", "GET",
		"pricing", "view",
		wrapV3(routes.GetProductPerformanceStockSizesHandler()),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/definitions", "GET",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductSeriesDefinitionsHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/definitions", "POST",
		"pricing", "update",
		wrapV3(http.HandlerFunc(routes.PostProductSeriesDefinitionHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/definitions/{id}", "PUT",
		"pricing", "update",
		wrapV3(http.HandlerFunc(routes.PutProductSeriesDefinitionHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/definitions/{id}", "DELETE",
		"pricing", "update",
		wrapV3(http.HandlerFunc(routes.DeleteProductSeriesDefinitionHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/mappings", "GET",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductSeriesMappingsHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/mappings/orphans", "GET",
		"pricing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductSeriesOrphanMappingsHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/product-series/mappings/save", "POST",
		"pricing", "update",
		wrapV3(http.HandlerFunc(routes.PostProductSeriesMappingsSaveHandler(pgDB))),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns", "GET",
		"pricing", "view",
		wrapV3(routes.GetWholesaleCampaignsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns/assignments", "GET",
		"pricing", "view",
		wrapV3(routes.GetWholesaleCampaignAssignmentsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns/variants", "GET",
		"pricing", "view",
		wrapV3(routes.GetWholesaleCampaignVariantStockHandler(mssql)),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns/variant-rows", "GET",
		"pricing", "view",
		wrapV3(routes.GetWholesaleCampaignVariantRowsHandler(pgDB, mssql)),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns/save", "POST",
		"pricing", "update",
		wrapV3(routes.SaveWholesaleCampaignAssignmentsHandler(pgDB, ml)),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns/{code}/campaign-history", "GET",
		"pricing", "view",
		wrapV3(routes.GetWholesaleCampaignHistoryHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/wholesale-campaigns/{code}/campaign-history/delete-selected", "POST",
		"pricing", "update",
		wrapV3(routes.PostDeleteSelectedWholesaleCampaignHistoryHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-classification/lookups", "GET",
		"pricing", "view",
		wrapV3(routes.GetBrandClassificationLookupsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-classification/brands", "GET",
		"pricing", "view",
		wrapV3(routes.ListBrandsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-classification/brands/sync", "POST",
		"pricing", "update",
		wrapV3(routes.SyncBrandsFromMSSQLHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-classification/brand/{code}/group", "POST",
		"pricing", "update",
		wrapV3(routes.SetBrandGroupHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-classification/brands/group-bulk", "POST",
		"pricing", "update",
		wrapV3(routes.SetBrandGroupsBulkHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-group-currency", "GET",
		"pricing", "view",
		wrapV3(routes.GetBrandGroupCurrencyHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/brand-group-currency/bulk-save", "POST",
		"pricing", "update",
		wrapV3(routes.SaveBrandGroupCurrencyHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/pricing-rules", "GET",
		"pricing", "view",
		wrapV3(routes.GetPricingRulesHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/pricing-rules/bulk-save", "POST",
		"pricing", "update",
		wrapV3(routes.SavePricingRulesBulkHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/pricing-rules/import", "POST",
		"pricing", "update",
		wrapV3(routes.ImportPricingRulesHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/pricing-rules/options", "GET",
		"pricing", "view",
		wrapV3(routes.GetPricingRuleOptionsHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/pricing-rules/parameters", "GET",
		"pricing", "view",
		wrapV3(routes.GetPricingParameterRulesHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/pricing-rules/export-all", "GET",
		"pricing", "view",
		wrapV3(routes.ExportPricingRulesHandler(pgDB)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/no-cost-products", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionNoCostProductsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-products", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostProductsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-history", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostHistoryHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail-groups", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailGroupsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail-header", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailHeaderHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/production-types", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionTypesHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/detail-editor-options", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailEditorOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail-exchange-rates", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailExchangeRatesHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail-line-history", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailLineHistoryHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail-similar-history", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailSimilarHistoryHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail-bulk-prices", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionHasCostDetailBulkPricesHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/has-cost-detail/last-detail", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingHasCostDetailLastDetailHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/options/hammadde-by-nos", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingOptionsHammaddeByNosHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/onml/save", "POST",
		"costing", "view",
		wrapV3(routes.PostProductionProductCostingOnMLSaveHandlerWithMailer(ml)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/onml/pdf", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingOnMLPDFHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/onml/delete", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingOnMLDeleteHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/default-quantities", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingDefaultQuantitiesHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/default-quantities/upsert", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingDefaultQuantitiesUpsertHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/default-quantities/update-bulk", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingDefaultQuantitiesBulkUpdateHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/default-quantities/calc-avg", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingDefaultQuantitiesCalcAvgHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/default-quantities/lookup", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingDefaultQuantitiesLookupHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/default-quantities/refresh", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingDefaultQuantitiesRefreshHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/tbstok/exists-bulk", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingTbStokExistsBulkHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/last10-warnings", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingLast10WarningsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/options/urun-ana-grup", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingUrunAnaGrupOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/options/urun-alt-grup", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingUrunAltGrupOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/options/urun-ana-alt-combos", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingUrunAnaAltCombosHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/options/mtbolum", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingMTBolumOptionsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/maliyet-parca-eslestirme", "GET",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.GetProductionProductCostingParcaMappingsHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/maliyet-parca-eslestirme", "DELETE",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.DeleteProductionProductCostingParcaMappingHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/maliyet-parca-eslestirme/upsert", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingParcaMappingUpsertHandler)),
	)
	bindV3(r, pgDB,
		"/api/pricing/production-product-costing/maliyet-parca-eslestirme/set-active", "POST",
		"costing", "view",
		wrapV3(http.HandlerFunc(routes.PostProductionProductCostingParcaMappingSetActiveHandler)),
	)

	// ============================================================
	// ROLE MANAGEMENT
	// ============================================================
	bindV3(r, pgDB,
		"/api/roles", "GET",
		"user", "view",
		wrapV3(routes.GetRolesHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/departments", "GET",
		"user", "view",
		wrapV3(routes.GetDepartmentsHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/piyasalar", "GET",
		"user", "view",
		wrapV3(routes.GetPiyasalarHandler(pgDB)),
	)

	// ============================================================
	// ROLE RELATIONS
	// ============================================================
	bindV3(r, pgDB,
		"/api/roles/{id}/departments", "POST",
		"user", "update",
		wrapV3(routes.UpdateRoleDepartmentsHandler(pgDB)),
	)

	bindV3(r, pgDB,
		"/api/roles/{id}/piyasalar", "POST",
		"user", "update",
		wrapV3(routes.UpdateRolePiyasalarHandler(pgDB)),
	)

	// ============================================================
	// USER ↔ ROLE
	// ============================================================
	bindV3(r, pgDB,
		"/api/users/{id}/roles", "POST",
		"user", "update",
		wrapV3(routes.UpdateUserRolesHandler(pgDB)),
	)

	// ============================================================
	// ADMIN EXTRA (eski main.go’da vardı, yeni sisteme alındı)
	// ============================================================
	bindV3(r, pgDB,
		"/api/admin/users/{id}/piyasa-sync", "POST",
		"admin", "user.update",
		wrapV3(http.HandlerFunc(routes.AdminSyncUserPiyasaHandler)),
	)

	return r
}

func main() {
	utils.InitSlog()
	slog.Info("backend start", "scope", "main")
	log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥")

	// -------------------------------------------------------
	// 🔑 ENV
	// -------------------------------------------------------
	// Önce .env + mail.env yükle. MSSQL başarısızsa .env.local dene.
	if err := godotenv.Load(".env", "mail.env"); err != nil {
		log.Println("⚠️ .env / mail.env bulunamadı")
	}

	// Local dev convenience: on Windows we generally want to override .env with .env.local
	// (e.g. SSH-tunnel PostgreSQL on 127.0.0.1:15432).
	if runtime.GOOS == "windows" {
		_ = godotenv.Overload(".env.local")
	}

	jwtSecret := os.Getenv("JWT_SECRET")
	if len(jwtSecret) < 10 {
		log.Fatal("❌ JWT_SECRET tanımlı değil veya çok kısa (min 10 karakter)")
	}
	log.Println("🔐 JWT_SECRET yüklendi")

	// -------------------------------------------------------
	// 🔗 DATABASE
	// -------------------------------------------------------
	if err := db.ConnectMSSQL(); err != nil {
		log.Println("⚠️ MSSQL ilk deneme başarısız:", err)
		if err2 := godotenv.Overload(".env.local"); err2 != nil {
			log.Println("⚠️ .env.local bulunamadı")
		}
		if err3 := db.ConnectMSSQL(); err3 != nil {
			log.Fatal(err3)
		}
	}
	if err := db.ConnectMSSQLUretim(); err != nil {
		if strings.Contains(err.Error(), "URETIM_MSSQL_CONN tanimli degil") {
			log.Println("⚠️ URETIM DB baglantisi atlandi: URETIM_MSSQL_CONN tanimli degil")
		} else {
			log.Fatal(err)
		}
	}

	pgDB, err := db.ConnectPostgres()
	if err != nil {
		log.Fatalf("❌ PostgreSQL bağlantı hatası: %v", err)
	}
	defer pgDB.Close()

	// -------------------------------------------------------
	// 🔐 ADMIN ROLE + DEPARTMENT AUTO SEED
	// -------------------------------------------------------
	if err := permissions.SeedAdminRoleDepartments(pgDB); err != nil {
		log.Println("❌ Admin dept seed failed:", err)
	} else {
		log.Println("✅ Admin dept permissions seeded")
	}

	// -------------------------------------------------------
	// 🕵️ AUDIT LOG INIT
	// -------------------------------------------------------
	auditlog.Init(pgDB, 1000)
	log.Println("🕵️ AuditLog sistemi başlatıldı (buffer=1000)")

	// -------------------------------------------------------
	// 🚀 TRANSLATION QUERY PERFORMANCE INDEXES
	// -------------------------------------------------------
	routes.EnsureTranslationPerfIndexes(pgDB)

	// -------------------------------------------------------
	// 🏷️ BRAND CLASSIFICATION BOOTSTRAP + SYNC SCHEDULER
	// -------------------------------------------------------
	// Ensure Postgres tables exist even before the UI hits the endpoints.
	if err := queries.EnsureBrandClassificationTables(pgDB); err != nil {
		log.Println("⚠️ mk_brands bootstrap failed:", err)
	}
	if err := queries.EnsurePricingRuleTables(pgDB); err != nil {
		log.Println("pricing rule tables bootstrap failed:", err)
	}
	if err := queries.EnsurePricingParameterTables(pgDB); err != nil {
		log.Println("mk_urunpricingprmtr bootstrap failed:", err)
	}
	if err := queries.EnsurePricingCalcInfraTables(pgDB); err != nil {
		log.Println("pricing calc infra bootstrap failed:", err)
	}
	if err := queries.EnsureProductSeriesAutoInfraTables(pgDB); err != nil {
		log.Println("product series auto infra bootstrap failed:", err)
	}
	if err := queries.EnsureProductPerformanceTables(pgDB); err != nil {
		log.Println("product performance infra bootstrap failed:", err)
	}

	// -------------------------------------------------------
	// ✉️ MAILER INIT
	// -------------------------------------------------------
	graphMailer, err := mailer.NewGraphMailer()
	if err != nil {
		log.Fatalf("❌ Graph Mailer init error: %v", err)
	}
	log.Println("✉️ Graph Mailer hazır")

	// -------------------------------------------------------
	// 👤 DEBUG
	// -------------------------------------------------------
	repository.NewUserRepository(pgDB).DebugListUsers()

	// -------------------------------------------------------
	// 🌍 SERVER
	// -------------------------------------------------------
	router := InitRoutes(pgDB, db.MssqlDB, graphMailer)
	startTranslationSyncScheduler(pgDB, db.MssqlDB)
	startBrandSyncScheduler(pgDB, db.MssqlDB)
	startPricingParameterSyncScheduler(pgDB, db.MssqlDB)
	startProductPricingCalcScheduler(pgDB)
	startProductPricingFxDeltaScheduler(pgDB)
	startProductPricingFxFullScheduler(pgDB)
	startProductSeriesAutoSchedulers(pgDB)
	startProductPerformanceScheduler(pgDB)

	handler := enableCORS(
		middlewares.GlobalAuthMiddleware(
			pgDB,
			middlewares.RequestLogger(router),
		),
	)

	host := strings.TrimSpace(os.Getenv("API_HOST"))
	port := strings.TrimSpace(os.Getenv("API_PORT"))

	if host == "" {
		host = "0.0.0.0"
	}
	if port == "" {
		port = "8080"
	}

	addr := host + ":" + port

	log.Println("🚀 Server running at:", addr)
	log.Fatal(http.ListenAndServe(addr, handler))

}

func mountUploads(r *mux.Router) {
	root := strings.TrimSpace(os.Getenv("BLOB_ROOT"))
	if root == "" {
		return
	}
	uploadsRoot := filepath.Join(root, "uploads")
	if fi, err := os.Stat(uploadsRoot); err != nil || !fi.IsDir() {
		return
	}
	fileServer := http.StripPrefix("/uploads/", http.FileServer(http.Dir(uploadsRoot)))
	r.PathPrefix("/uploads/").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet && r.Method != http.MethodHead {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Cache-Control", "public, max-age=3600")
		fileServer.ServeHTTP(w, r)
	})).Methods(http.MethodGet, http.MethodHead, http.MethodOptions)
}

func mountSPA(r *mux.Router) {
	r.NotFoundHandler = http.HandlerFunc(spaIndex)
	r.HandleFunc("/", spaIndex).Methods(http.MethodGet)
}

func spaIndex(w http.ResponseWriter, r *http.Request) {
	uiDir := uiRootDir()
	p := r.URL.Path

	if r.URL.Path == "/logo.png" {
		_, err := os.Stat("./logo.png")
		if err == nil {
			http.ServeFile(w, r, "./logo.png")
			return
		}
	}

	if !strings.HasPrefix(p, "/") {
		p = "/" + p
		r.URL.Path = p
	}
	p = path.Clean(p)
	if p == "/" {
		p = "index.html"
	}

	if strings.HasPrefix(p, "/api") {
		http.NotFound(w, r)
		return
	}

	name := path.Join(uiDir, filepath.FromSlash(p))

	f, err := os.Stat(name)
	if err != nil {
		if os.IsNotExist(err) {
			http.ServeFile(w, r, filepath.Join(uiDir, "index.html"))
			return
		}

		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	if f.IsDir() {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	http.ServeFile(w, r, name)
}

func uiRootDir() string {
	if d := strings.TrimSpace(os.Getenv("UI_DIR")); d != "" {
		return d
	}

	candidates := []string{
		"../ui/dist/spa",
		"./ui/dist/spa",
		"../ui/dist",
		"./ui/dist",
	}
	for _, d := range candidates {
		if fi, err := os.Stat(d); err == nil && fi.IsDir() {
			return d
		}
	}

	return "../ui/dist/spa"
}
