package routes

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/google/uuid"
	"github.com/gorilla/mux"
)

type ProductImageItem struct {
	ID           int64  `json:"id"`
	FileName     string `json:"file_name"`
	FileSize     int64  `json:"file_size"`
	Storage      string `json:"storage_path"`
	ContentURL   string `json:"content_url"`
	UUID         string `json:"uuid,omitempty"`
	ThumbURL     string `json:"thumb_url,omitempty"`
	FullURL      string `json:"full_url,omitempty"`
	StoredInDB   bool   `json:"stored_in_db,omitempty"`
	HasDBContent bool   `json:"-"`
}

type ProductImageBatchItem struct {
	Key   string `json:"key"`
	Code  string `json:"code"`
	Dim1  string `json:"dim1"`
	Dim3  string `json:"dim3"`
	Limit int    `json:"limit,omitempty"`
}

type ProductImageBatchRequest struct {
	Items []ProductImageBatchItem `json:"items"`
}

type ProductImageBatchResponseItem struct {
	Key    string             `json:"key"`
	Images []ProductImageItem `json:"images"`
}

var uuidPattern = regexp.MustCompile(`(?i)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)

func normalizeDimParam(v string) string {
	s := strings.TrimSpace(v)
	if s == "" || s == "0" {
		return ""
	}
	return s
}

func uniqueNonEmpty(items ...string) []string {
	out := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, it := range items {
		v := normalizeDimParam(it)
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		out = append(out, v)
	}
	return out
}

func buildNameLikePatterns(token string) []string {
	t := strings.ToUpper(strings.TrimSpace(token))
	if t == "" {
		return nil
	}
	return []string{
		"% " + t + " %",
		"%-" + t + "-%",
		"%-" + t + "_%",
		"%_" + t + "_%",
		"%(" + t + ")%",
		t + " %",
	}
}

func resolveDimvalFromFileNameToken(pg *sql.DB, column, token string) string {
	patterns := buildNameLikePatterns(token)
	if len(patterns) == 0 {
		return ""
	}
	query := fmt.Sprintf(`
SELECT x.dimv
FROM (
    SELECT COALESCE(%s::text, '') AS dimv, COUNT(*) AS cnt
    FROM dfblob
    WHERE src_table='mmitem'
      AND typ='img'
      AND COALESCE(%s::text, '') <> ''
      AND (
        UPPER(COALESCE(file_name,'')) LIKE $1 OR
        UPPER(COALESCE(file_name,'')) LIKE $2 OR
        UPPER(COALESCE(file_name,'')) LIKE $3 OR
        UPPER(COALESCE(file_name,'')) LIKE $4 OR
        UPPER(COALESCE(file_name,'')) LIKE $5 OR
        UPPER(COALESCE(file_name,'')) LIKE $6
      )
    GROUP BY COALESCE(%s::text, '')
) x
ORDER BY x.cnt DESC, x.dimv
LIMIT 1
`, column, column, column)
	var v string
	if err := pg.QueryRow(query,
		patterns[0],
		patterns[1],
		patterns[2],
		patterns[3],
		patterns[4],
		patterns[5],
	).Scan(&v); err != nil {
		return ""
	}
	return normalizeDimParam(v)
}

func extractImageUUID(storagePath, fileName string) string {
	if m := uuidPattern.FindString(storagePath); m != "" {
		return strings.ToLower(m)
	}
	if m := uuidPattern.FindString(fileName); m != "" {
		return strings.ToLower(m)
	}
	return ""
}

func enrichProductImageItem(it *ProductImageItem) {
	if it == nil {
		return
	}
	if u := extractImageUUID(it.Storage, it.FileName); u != "" {
		it.UUID = u
		it.ThumbURL = productImagePublicURLIfExists("uploads/image/t300/" + u + ".jpg")
		it.FullURL = productImagePublicURLIfExists("uploads/image/" + u + ".jpg")
	}
	if it.FullURL == "" {
		it.FullURL = productImagePublicURLIfExists(it.Storage)
	}
	if it.FullURL == "" {
		it.FullURL = productImagePublicURLIfExists(it.FileName)
	}
	if it.ID > 0 && (it.HasDBContent || productImageStoredFileExists(it.Storage, it.FileName)) {
		it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
	}
}

func productImageItemHasURL(it *ProductImageItem) bool {
	return it != nil && (it.ThumbURL != "" || it.FullURL != "" || it.ContentURL != "")
}

func productImageStoredFileExists(storagePath, fileName string) bool {
	if resolved, _ := resolveStoragePath(storagePath); resolved != "" {
		return true
	}
	if resolved, _ := resolveStoragePath(fileName); resolved != "" {
		return true
	}
	return false
}

func productImagePublicURLIfExists(storagePath string) string {
	raw := strings.TrimSpace(storagePath)
	if raw == "" {
		return ""
	}
	resolved, _ := resolveStoragePath(raw)
	if resolved == "" {
		return ""
	}

	normalized := strings.ReplaceAll(raw, "\\", "/")
	lower := strings.ToLower(normalized)
	if idx := strings.Index(lower, "/uploads/"); idx >= 0 {
		return normalized[idx:]
	}
	if strings.HasPrefix(lower, "uploads/") {
		return "/" + normalized
	}

	root := strings.TrimSpace(os.Getenv("BLOB_ROOT"))
	if root == "" {
		return ""
	}
	rel, err := filepath.Rel(root, resolved)
	if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
		return ""
	}
	rel = filepath.ToSlash(rel)
	if strings.HasPrefix(strings.ToLower(rel), "uploads/") {
		return "/" + rel
	}
	return ""
}

// POST /api/product-images/batch
func PostProductImagesBatchHandler(pg *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
		if reqID == "" {
			reqID = uuid.NewString()
		}
		w.Header().Set("X-Request-ID", reqID)
		w.Header().Set("Content-Type", "application/json; charset=utf-8")

		var req ProductImageBatchRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, "Gecersiz gorsel batch istegi: "+err.Error(), http.StatusBadRequest)
			return
		}
		if len(req.Items) > 300 {
			req.Items = req.Items[:300]
		}

		cleanItems := make([]ProductImageBatchItem, 0, len(req.Items))
		seen := map[string]bool{}
		for _, item := range req.Items {
			item.Key = strings.TrimSpace(item.Key)
			item.Code = strings.TrimSpace(item.Code)
			item.Dim1 = normalizeDimParam(item.Dim1)
			item.Dim3 = normalizeDimParam(item.Dim3)
			if item.Key == "" {
				item.Key = item.Code + "|" + item.Dim1 + "|" + item.Dim3
			}
			if item.Code == "" || seen[item.Key] {
				continue
			}
			seen[item.Key] = true
			cleanItems = append(cleanItems, item)
		}
		if len(cleanItems) == 0 {
			_ = json.NewEncoder(w).Encode([]ProductImageBatchResponseItem{})
			return
		}

		payload, err := json.Marshal(cleanItems)
		if err != nil {
			http.Error(w, "Gorsel batch hazirlanamadi: "+err.Error(), http.StatusInternalServerError)
			return
		}

		rows, err := pg.Query(`
WITH req AS (
  SELECT *
  FROM jsonb_to_recordset($1::jsonb) AS x(key text, code text, dim1 text, dim3 text)
),
matched_item AS (
  SELECT DISTINCT ON (r.key)
    r.key,
    r.code,
    r.dim1,
    r.dim3,
    m.id AS mmitem_id
  FROM req r
  JOIN mmitem m ON
    UPPER(REPLACE(COALESCE(m.code,''), ' ', '')) = UPPER(REPLACE(COALESCE(r.code,''), ' ', ''))
    OR UPPER(REPLACE(REGEXP_REPLACE(COALESCE(m.code,''), '^.*-', ''), ' ', '')) =
       UPPER(REPLACE(REGEXP_REPLACE(COALESCE(r.code,''), '^.*-', ''), ' ', ''))
  ORDER BY r.key, m.id
),
ranked AS (
  SELECT
    mi.key,
    b.id,
    COALESCE(b.file_name,'') AS file_name,
    COALESCE(b.file_size,0) AS file_size,
    COALESCE(b.storage_path,'') AS storage_path,
    COALESCE(b.stored_in_db,false) AS stored_in_db,
    (COALESCE(octet_length(b.bin),0) > 0) AS has_db_content,
    ROW_NUMBER() OVER (
      PARTITION BY mi.key
      ORDER BY
        CASE
          WHEN mi.dim1 <> '' AND mi.dim3 <> '' AND COALESCE(b.dimval1::text,'') = mi.dim1 AND COALESCE(b.dimval3::text,'') = mi.dim3 THEN 0
          WHEN mi.dim1 <> '' AND COALESCE(b.dimval1::text,'') = mi.dim1 AND COALESCE(b.dimval3::text,'') = '' THEN 1
          WHEN COALESCE(b.dimval1::text,'') = '' AND COALESCE(b.dimval3::text,'') = '' THEN 2
          ELSE 3
        END,
        COALESCE(b.sort_order,999999),
        b.id
    ) AS rn
  FROM matched_item mi
  JOIN dfblob b ON b.src_table='mmitem'
    AND b.typ='img'
    AND b.src_id=mi.mmitem_id
)
SELECT key, id, file_name, file_size, storage_path, stored_in_db, has_db_content
FROM ranked
WHERE rn <= 5
ORDER BY key, rn
`, string(payload))
		if err != nil {
			slog.Error("product_images.batch.query_failed", "req_id", reqID, "err", err.Error())
			http.Error(w, "Gorsel batch sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		grouped := map[string][]ProductImageItem{}
		for rows.Next() {
			var key string
			var it ProductImageItem
			if err := rows.Scan(&key, &it.ID, &it.FileName, &it.FileSize, &it.Storage, &it.StoredInDB, &it.HasDBContent); err != nil {
				continue
			}
			enrichProductImageItem(&it)
			if !productImageItemHasURL(&it) {
				continue
			}
			grouped[key] = append(grouped[key], it)
		}
		if err := rows.Err(); err != nil {
			http.Error(w, "Gorsel batch okuma hatasi: "+err.Error(), http.StatusInternalServerError)
			return
		}

		out := make([]ProductImageBatchResponseItem, 0, len(cleanItems))
		for _, item := range cleanItems {
			out = append(out, ProductImageBatchResponseItem{
				Key:    item.Key,
				Images: grouped[item.Key],
			})
		}
		_ = json.NewEncoder(w).Encode(out)
	}
}

// GET /api/product-images?code=...&dim1=...&dim3=...
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
		if reqID == "" {
			reqID = uuid.NewString()
		}
		w.Header().Set("X-Request-ID", reqID)

		code := strings.TrimSpace(r.URL.Query().Get("code"))
		dim1 := strings.TrimSpace(r.URL.Query().Get("dim1"))
		if dim1 == "" {
			dim1 = strings.TrimSpace(r.URL.Query().Get("color"))
		}
		dim3 := strings.TrimSpace(r.URL.Query().Get("dim3"))
		if dim3 == "" {
			dim3 = strings.TrimSpace(r.URL.Query().Get("yaka"))
		}
		if dim3 == "" {
			dim3 = strings.TrimSpace(r.URL.Query().Get("renk2"))
		}

		dim1ID := strings.TrimSpace(r.URL.Query().Get("dim1_id"))
		if dim1ID == "" {
			dim1ID = strings.TrimSpace(r.URL.Query().Get("itemdim1"))
		}
		dim3ID := strings.TrimSpace(r.URL.Query().Get("dim3_id"))
		if dim3ID == "" {
			dim3ID = strings.TrimSpace(r.URL.Query().Get("itemdim3"))
		}

		if code == "" {
			http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
			return
		}

		// Rule: code -> mmitem.id
		var mmItemID int64
		err := pg.QueryRow(`
SELECT id
FROM mmitem
WHERE UPPER(REPLACE(COALESCE(code,''), ' ', '')) = UPPER(REPLACE(COALESCE($1,''), ' ', ''))
ORDER BY id
LIMIT 1
`, code).Scan(&mmItemID)
		if err == sql.ErrNoRows {
			err = pg.QueryRow(`
SELECT id
FROM mmitem
WHERE UPPER(REPLACE(REGEXP_REPLACE(COALESCE(code,''), '^.*-', ''), ' ', '')) =
      UPPER(REPLACE(REGEXP_REPLACE(COALESCE($1,''), '^.*-', ''), ' ', ''))
ORDER BY id
LIMIT 1
`, code).Scan(&mmItemID)
		}
		if err != nil {
			if err == sql.ErrNoRows {
				w.Header().Set("Content-Type", "application/json; charset=utf-8")
				_ = json.NewEncoder(w).Encode([]ProductImageItem{})
				return
			}
			http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
			return
		}

		runQuery := func(dim1Filter, dim3Filter string) ([]ProductImageItem, error) {
			query := `
SELECT
  id,
  COALESCE(file_name,'') AS file_name,
  COALESCE(file_size,0) AS file_size,
  COALESCE(storage_path,'') AS storage_path,
  COALESCE(stored_in_db,false) AS stored_in_db,
  (COALESCE(octet_length(bin),0) > 0) AS has_db_content
FROM dfblob
WHERE typ='img'
  AND src_table='mmitem'
  AND src_id=$1`
			args := []interface{}{mmItemID}
			argPos := 2
			if dim1Filter != "" {
				query += fmt.Sprintf(" AND COALESCE(dimval1::text,'') = $%d", argPos)
				args = append(args, dim1Filter)
				argPos++
				if dim3Filter != "" {
					query += fmt.Sprintf(" AND COALESCE(dimval3::text,'') = $%d", argPos)
					args = append(args, dim3Filter)
					argPos++
				}
			}
			query += `
ORDER BY
  COALESCE(sort_order,999999),
  id`

			rows, err := pg.Query(query, args...)
			if err != nil {
				return nil, err
			}
			defer rows.Close()

			items := make([]ProductImageItem, 0, 16)
			for rows.Next() {
				var it ProductImageItem
				if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage, &it.StoredInDB, &it.HasDBContent); err != nil {
					continue
				}
				enrichProductImageItem(&it)
				if productImageItemHasURL(&it) {
					items = append(items, it)
				}
			}
			if err := rows.Err(); err != nil {
				return nil, err
			}
			return items, nil
		}

		// Rule:
		// dim1!=0 && dim3!=0 => dimval1=dim1 AND dimval3=dim3
		// dim1!=0 && dim3==0 => dimval1=dim1
		// dim1==0 && dim3==0 => generic photos
		//
		// Frontend'den yanlis dim id gelebildigi icin:
		// 1) once *_id ile deneriz
		// 2) sonuc yoksa kod degeriyle fallback deneriz.
		resolvedDim1ID := normalizeDimParam(dim1ID)
		if resolvedDim1ID == "" && normalizeDimParam(dim1) != "" {
			resolvedDim1ID = resolveDimvalFromFileNameToken(pg, "dimval1", dim1)
		}
		resolvedDim3ID := normalizeDimParam(dim3ID)
		if resolvedDim3ID == "" && normalizeDimParam(dim3) != "" {
			resolvedDim3ID = resolveDimvalFromFileNameToken(pg, "dimval3", dim3)
		}

		dim1Candidates := uniqueNonEmpty(resolvedDim1ID, dim1ID, dim1)
		if len(dim1Candidates) == 0 {
			dim1Candidates = []string{""}
		}
		dim3Candidates := uniqueNonEmpty(resolvedDim3ID, dim3ID, dim3)

		items := make([]ProductImageItem, 0, 16)
		selectedDim1 := ""
		selectedDim3 := ""
		var queryErr error

		for _, d1 := range dim1Candidates {
			localDim3Candidates := []string{""}
			if d1 != "" {
				if len(dim3Candidates) > 0 {
					localDim3Candidates = append([]string{}, dim3Candidates...)
					localDim3Candidates = append(localDim3Candidates, "")
				}
			}

			for _, d3 := range localDim3Candidates {
				var runErr error
				items, runErr = runQuery(d1, d3)
				if runErr != nil {
					queryErr = runErr
					continue
				}
				if len(items) > 0 {
					selectedDim1 = d1
					selectedDim3 = d3
					break
				}
				if selectedDim1 == "" && selectedDim3 == "" {
					selectedDim1 = d1
					selectedDim3 = d3
				}
			}
			if len(items) > 0 {
				break
			}
		}

		if queryErr != nil && len(items) == 0 {
			slog.Error("product_images.list.query_failed",
				"req_id", reqID,
				"code", code,
				"dim1", dim1,
				"dim1_id", dim1ID,
				"dim3", dim3,
				"dim3_id", dim3ID,
				"err", queryErr.Error(),
			)
			http.Error(w, "Gorsel sorgu hatasi: "+queryErr.Error(), http.StatusInternalServerError)
			return
		}

		slog.Info("product_images.list.ok",
			"req_id", reqID,
			"code", code,
			"dim1", dim1,
			"dim1_id", dim1ID,
			"resolved_dim1_id", resolvedDim1ID,
			"dim3", dim3,
			"dim3_id", dim3ID,
			"resolved_dim3_id", resolvedDim3ID,
			"selected_dim1", selectedDim1,
			"selected_dim3", selectedDim3,
			"count", len(items),
		)

		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		_ = json.NewEncoder(w).Encode(items)
	}
}

// GET /api/product-images/{id}/content
func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
		if reqID == "" {
			reqID = uuid.NewString()
		}
		w.Header().Set("X-Request-ID", reqID)

		idStr := mux.Vars(r)["id"]
		id, err := strconv.ParseInt(idStr, 10, 64)
		if err != nil || id <= 0 {
			http.Error(w, "Gecersiz gorsel id", http.StatusBadRequest)
			return
		}

		var (
			fileName    string
			storagePath string
			storedInDB  bool
			binData     []byte
		)

		err = pg.QueryRow(`
SELECT
  COALESCE(file_name,''),
  COALESCE(storage_path,''),
  COALESCE(stored_in_db,false),
  bin
FROM dfblob
WHERE id = $1
  AND typ = 'img'
`, id).Scan(&fileName, &storagePath, &storedInDB, &binData)
		if err != nil {
			if err == sql.ErrNoRows {
				http.NotFound(w, r)
				return
			}
			http.Error(w, "Gorsel okunamadi: "+err.Error(), http.StatusInternalServerError)
			return
		}

		_ = storedInDB
		if len(binData) > 0 {
			w.Header().Set("Content-Type", http.DetectContentType(binData))
			w.Header().Set("Cache-Control", "public, max-age=3600")
			_, _ = w.Write(binData)
			return
		}

		resolved, _ := resolveStoragePath(storagePath)
		if resolved == "" {
			resolved, _ = resolveStoragePath(fileName)
		}
		if resolved == "" {
			http.NotFound(w, r)
			return
		}

		w.Header().Set("Cache-Control", "public, max-age=3600")
		http.ServeFile(w, r, resolved)
	}
}

func resolveStoragePath(storagePath string) (string, []string) {
	raw := strings.TrimSpace(storagePath)
	if raw == "" {
		return "", nil
	}
	if i := strings.Index(raw, "?"); i >= 0 {
		raw = raw[:i]
	}

	raw = strings.ReplaceAll(raw, "\\", "/")
	if scheme := strings.Index(raw, "://"); scheme >= 0 {
		rest := raw[scheme+3:]
		if i := strings.Index(rest, "/"); i >= 0 {
			raw = rest[i:]
		}
	}

	raw = strings.TrimPrefix(raw, "./")
	raw = strings.TrimPrefix(raw, "/")
	raw = strings.TrimPrefix(raw, "uploads/")
	raw = filepath.ToSlash(filepath.Clean(raw))

	relUploads := filepath.FromSlash(filepath.Join("uploads", raw))
	baseName := filepath.Base(raw)
	relImage := filepath.FromSlash(filepath.Join("uploads", "image", baseName))
	relImageThumb := filepath.FromSlash(filepath.Join("uploads", "image", "t300", baseName))
	candidates := []string{
		filepath.Clean(storagePath),
		filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))),
		filepath.FromSlash(filepath.Clean(raw)),
		relUploads,
		relImage,
		relImageThumb,
		filepath.Join(".", relUploads),
		filepath.Join(".", relImage),
		filepath.Join(".", relImageThumb),
		filepath.Join("..", relUploads),
		filepath.Join("..", relImage),
		filepath.Join("..", relImageThumb),
		filepath.Join("..", "..", relUploads),
		filepath.Join("..", "..", relImage),
		filepath.Join("..", "..", relImageThumb),
	}

	if u := extractImageUUID(raw, baseName); u != "" {
		uFull := filepath.FromSlash(filepath.Join("uploads", "image", u+".jpg"))
		uThumb := filepath.FromSlash(filepath.Join("uploads", "image", "t300", u+".jpg"))
		candidates = append(candidates,
			filepath.FromSlash(filepath.Join("image", u+".jpg")),
			filepath.FromSlash(filepath.Join("image", "t300", u+".jpg")),
			uFull,
			uThumb,
			filepath.Join(".", uFull),
			filepath.Join(".", uThumb),
			filepath.Join("..", uFull),
			filepath.Join("..", uThumb),
			filepath.Join("..", "..", uFull),
			filepath.Join("..", "..", uThumb),
		)
	}

	if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
		candidates = append(candidates,
			filepath.Join(root, raw),
			filepath.Join(root, relUploads),
			filepath.Join(root, "uploads", raw),
			filepath.Join(root, relImage),
			filepath.Join(root, relImageThumb),
		)
		if u := extractImageUUID(raw, baseName); u != "" {
			candidates = append(candidates,
				filepath.Join(root, "uploads", "image", u+".jpg"),
				filepath.Join(root, "uploads", "image", "t300", u+".jpg"),
				filepath.Join(root, "image", u+".jpg"),
				filepath.Join(root, "image", "t300", u+".jpg"),
			)
		}
	}

	for _, p := range candidates {
		if p == "" {
			continue
		}
		if st, err := os.Stat(p); err == nil && !st.IsDir() {
			return p, candidates
		}
	}

	return "", candidates
}
