package catalog

import (
	"context"
	"database/sql"
	"fmt"
	"net/http"
	"strings"

	"gorm.io/gorm"
)

type ProductPageRequest struct {
	ID         int64
	Slug       string
	LanguageID int64
	ShopID     int64
}

type CategoryPageRequest struct {
	ID         int64
	Slug       string
	LanguageID int64
	ShopID     int64
}

type ProductPageData struct {
	ID               int64
	Name             string
	Slug             string
	ShortDescription string
	Description      string
	Price            float64
	CoverImageID     sql.NullInt64
	CategoryID       int64
	CategorySlug     string
	CategoryName     string
}

type CategoryPageData struct {
	ID          int64
	Name        string
	Slug        string
	Description string
	Products    []CategoryProductCard `gorm:"-"`
}

type CategoryProductCard struct {
	ID          int64
	Name        string
	Slug        string
	URL         string `gorm:"-"`
	Price       float64
	Description string
	EAN13       string
}

type MenuItem struct {
	ID       int64
	ParentID int64
	Name     string
	Slug     string
	Depth    int
	URL      string     `gorm:"-"`
	Children []MenuItem `gorm:"-"`
}

type LocaleOption struct {
	ID         int64
	CurrencyID int64 `gorm:"column:currency_id"`
	Label      string
	Code       string
	Meta       string
	URL        string `gorm:"-"`
}

type HeaderLocaleData struct {
	CurrentLanguage LocaleOption
	CurrentCountry  LocaleOption
	Languages       []LocaleOption
	Countries       []LocaleOption
}

type Service struct {
	db     *gorm.DB
	prefix string
}

func NewService(db *gorm.DB, prefix string) *Service {
	return &Service{db: db, prefix: prefix}
}

func (s *Service) GetProductPage(ctx context.Context, req ProductPageRequest) (*ProductPageData, error) {
	var product ProductPageData
	queryByID := fmt.Sprintf(`
SELECT p.id_product AS id,
       pl.name AS name,
       pl.link_rewrite AS slug,
       pl.description_short AS short_description,
       pl.description AS description,
       ps.price AS price,
       i.id_image AS cover_image_id,
       p.id_category_default AS category_id,
       cl.link_rewrite AS category_slug,
       cl.name AS category_name
FROM %sproduct p
JOIN %sproduct_lang pl ON pl.id_product = p.id_product
JOIN %sproduct_shop ps ON ps.id_product = p.id_product
LEFT JOIN %scategory_lang cl ON cl.id_category = p.id_category_default AND cl.id_lang = pl.id_lang
LEFT JOIN %simage i ON i.id_product = p.id_product AND i.cover = 1
WHERE p.id_product = ?
  AND pl.id_lang = ?
  AND ps.id_shop = ?
LIMIT 1
`, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix)
	queryBySlug := fmt.Sprintf(`
SELECT p.id_product AS id,
       pl.name AS name,
       pl.link_rewrite AS slug,
       pl.description_short AS short_description,
       pl.description AS description,
       ps.price AS price,
       i.id_image AS cover_image_id,
       p.id_category_default AS category_id,
       cl.link_rewrite AS category_slug,
       cl.name AS category_name
FROM %sproduct p
JOIN %sproduct_lang pl ON pl.id_product = p.id_product
JOIN %sproduct_shop ps ON ps.id_product = p.id_product
LEFT JOIN %scategory_lang cl ON cl.id_category = p.id_category_default AND cl.id_lang = pl.id_lang
LEFT JOIN %simage i ON i.id_product = p.id_product AND i.cover = 1
WHERE pl.link_rewrite = ?
  AND pl.id_lang = ?
  AND ps.id_shop = ?
LIMIT 1
`, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix)

	var result *gorm.DB
	if req.ID != 0 {
		result = s.db.WithContext(ctx).Raw(strings.TrimSpace(queryByID), req.ID, req.LanguageID, req.ShopID).Scan(&product)
	} else {
		result = s.db.WithContext(ctx).Raw(strings.TrimSpace(queryBySlug), req.Slug, req.LanguageID, req.ShopID).Scan(&product)
	}
	if result.Error != nil {
		return nil, result.Error
	}
	if result.RowsAffected == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	return &product, nil
}

func (s *Service) GetCategoryPage(ctx context.Context, req CategoryPageRequest) (*CategoryPageData, error) {
	var category CategoryPageData
	categoryQuery := fmt.Sprintf(`
SELECT c.id_category AS id,
       cl.name AS name,
       cl.link_rewrite AS slug,
       cl.description AS description
FROM %scategory c
JOIN %scategory_lang cl ON cl.id_category = c.id_category
WHERE c.id_category = ?
  AND cl.id_lang = ?
LIMIT 1
`, s.prefix, s.prefix)
	categoryFallbackQuery := fmt.Sprintf(`
SELECT c.id_category AS id,
       cl.name AS name,
       cl.link_rewrite AS slug,
       cl.description AS description
FROM %scategory c
JOIN %scategory_lang cl ON cl.id_category = c.id_category
WHERE c.id_category = ?
ORDER BY cl.id_lang ASC
LIMIT 1
`, s.prefix, s.prefix)

	lookupID := req.ID
	if lookupID == 0 {
		idQuery := fmt.Sprintf(`
SELECT c.id_category
FROM %scategory c
JOIN %scategory_lang cl ON cl.id_category = c.id_category
WHERE cl.link_rewrite = ?
  AND cl.id_lang = ?
LIMIT 1
`, s.prefix, s.prefix)
		var row struct {
			ID int64 `gorm:"column:id_category"`
		}
		result := s.db.WithContext(ctx).Raw(strings.TrimSpace(idQuery), req.Slug, req.LanguageID).Scan(&row)
		if result.Error != nil {
			return nil, result.Error
		}
		if result.RowsAffected == 0 {
			return nil, gorm.ErrRecordNotFound
		}
		lookupID = row.ID
	}

	result := s.db.WithContext(ctx).Raw(strings.TrimSpace(categoryQuery), lookupID, req.LanguageID).Scan(&category)
	if result.Error != nil {
		return nil, result.Error
	}
	if result.RowsAffected == 0 {
		result = s.db.WithContext(ctx).Raw(strings.TrimSpace(categoryFallbackQuery), lookupID).Scan(&category)
		if result.Error != nil {
			return nil, result.Error
		}
		if result.RowsAffected == 0 {
			return nil, gorm.ErrRecordNotFound
		}
	}

	productQuery := fmt.Sprintf(`
SELECT p.id_product AS id,
       pl.name AS name,
       pl.link_rewrite AS slug,
       p.ean13 AS ean13,
       ps.price AS price,
       pl.description_short AS description
FROM %scategory_product cp
JOIN %sproduct p ON p.id_product = cp.id_product
JOIN %sproduct_lang pl ON pl.id_product = p.id_product
JOIN %sproduct_shop ps ON ps.id_product = p.id_product
WHERE cp.id_category = ?
  AND pl.id_lang = ?
  AND ps.id_shop = ?
ORDER BY cp.position ASC, p.id_product ASC
LIMIT 48
`, s.prefix, s.prefix, s.prefix, s.prefix)

	if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(productQuery), category.ID, req.LanguageID, req.ShopID).Scan(&category.Products).Error; err != nil {
		return nil, err
	}

	return &category, nil
}

func (s *Service) ResolveLanguageID(ctx context.Context, req *http.Request, fallback int64) int64 {
	if req == nil || req.URL == nil {
		return fallback
	}
	path := strings.Trim(req.URL.Path, "/")
	if path == "" {
		return fallback
	}
	first := path
	if idx := strings.IndexByte(path, '/'); idx >= 0 {
		first = path[:idx]
	}
	first = strings.TrimSpace(first)
	if len(first) < 2 || len(first) > 5 {
		return fallback
	}

	var row struct {
		ID int64 `gorm:"column:id_lang"`
	}
	query := fmt.Sprintf("SELECT id_lang FROM %slang WHERE iso_code = ? LIMIT 1", s.prefix)
	result := s.db.WithContext(ctx).Raw(query, strings.ToUpper(first)).Scan(&row)
	if result.Error != nil || result.RowsAffected == 0 || row.ID == 0 {
		return fallback
	}
	return row.ID
}

func (s *Service) GetCategoryMenu(ctx context.Context, languageID int64, shopID int64) ([]MenuItem, error) {
	rootCategoryID, err := s.rootCategoryID(ctx)
	if err != nil {
		return nil, err
	}

	query := fmt.Sprintf(`
WITH RECURSIVE category_tree AS (
	SELECT c.id_category AS id,
	       c.id_parent AS parent_id,
	       cl.name AS name,
	       cl.link_rewrite AS slug,
	       0 AS depth
	FROM %scategory c
	JOIN %scategory_shop cs ON cs.id_category = c.id_category
	JOIN %scategory_lang cl ON cl.id_category = c.id_category
	WHERE c.id_parent = ?
	  AND c.active = 1
	  AND cs.id_shop = ?
	  AND cl.id_lang = ?
	UNION ALL
	SELECT c.id_category AS id,
	       c.id_parent AS parent_id,
	       cl.name AS name,
	       cl.link_rewrite AS slug,
	       tree.depth + 1 AS depth
	FROM %scategory c
	JOIN %scategory_shop cs ON cs.id_category = c.id_category
	JOIN %scategory_lang cl ON cl.id_category = c.id_category
	JOIN category_tree tree ON tree.id = c.id_parent
	WHERE c.active = 1
	  AND cs.id_shop = ?
	  AND cl.id_lang = ?
)
SELECT id, parent_id, name, slug, depth
FROM category_tree
ORDER BY depth ASC, parent_id ASC, name ASC
`, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix)

	var flat []MenuItem
	if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(query), rootCategoryID, shopID, languageID, shopID, languageID).Scan(&flat).Error; err != nil {
		return nil, err
	}
	if len(flat) == 0 {
		return nil, nil
	}

	nodes := make(map[int64]MenuItem, len(flat))
	childrenByParent := make(map[int64][]int64, len(flat))
	for i := range flat {
		item := flat[i]
		nodes[item.ID] = item
		childrenByParent[item.ParentID] = append(childrenByParent[item.ParentID], item.ID)
	}

	rootIDs := childrenByParent[rootCategoryID]
	roots := make([]MenuItem, 0, len(rootIDs))
	for _, id := range rootIDs {
		if item, ok := buildMenuTree(id, nodes, childrenByParent); ok {
			roots = append(roots, item)
		}
	}
	return roots, nil
}

func (s *Service) GetHeaderLocale(ctx context.Context, languageID int64, currencyID int64, countryISO string) (HeaderLocaleData, error) {
	var locale HeaderLocaleData
	defaultCurrencyID, err := s.defaultCurrencyID(ctx)
	if err != nil {
		return HeaderLocaleData{}, err
	}

	languageQuery := fmt.Sprintf(`
SELECT id_lang AS id,
       name AS label,
       UPPER(iso_code) AS code,
       COALESCE(NULLIF(language_code, ''), UPPER(iso_code)) AS meta
FROM %slang
WHERE active = 1
ORDER BY id_lang ASC
`, s.prefix)
	if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(languageQuery)).Scan(&locale.Languages).Error; err != nil {
		return HeaderLocaleData{}, err
	}

	hasCountryCurrency, err := s.columnExists(ctx, s.prefix+"country", "id_currency")
	if err != nil {
		return HeaderLocaleData{}, err
	}

	var countryQuery string
	var countryArgs []any
	if hasCountryCurrency {
		countryQuery = fmt.Sprintf(`
SELECT c.id_country AS id,
       COALESCE(c.id_currency, ?) AS currency_id,
       cl.name AS label,
       UPPER(c.iso_code) AS code,
       TRIM(CONCAT(COALESCE(UPPER(cur.iso_code), ''), ' ', COALESCE(cur.sign, ''))) AS meta
FROM %scountry c
JOIN %scountry_lang cl ON cl.id_country = c.id_country
LEFT JOIN %scurrency cur ON cur.id_currency = c.id_currency
WHERE c.active = 1
  AND cl.id_lang = ?
ORDER BY cl.name ASC
`, s.prefix, s.prefix, s.prefix)
		countryArgs = []any{defaultCurrencyID, languageID}
	} else {
		countryQuery = fmt.Sprintf(`
SELECT c.id_country AS id,
       ? AS currency_id,
       cl.name AS label,
       UPPER(c.iso_code) AS code,
       UPPER(c.iso_code) AS meta
FROM %scountry c
JOIN %scountry_lang cl ON cl.id_country = c.id_country
WHERE c.active = 1
  AND cl.id_lang = ?
ORDER BY cl.name ASC
`, s.prefix, s.prefix)
		countryArgs = []any{defaultCurrencyID, languageID}
	}
	if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(countryQuery), countryArgs...).Scan(&locale.Countries).Error; err != nil {
		return HeaderLocaleData{}, err
	}

	locale.CurrentLanguage = pickLocaleOptionByID(locale.Languages, languageID)
	locale.CurrentCountry = pickLocaleOptionByCode(locale.Countries, countryISO)
	return locale, nil
}

func (s *Service) rootCategoryID(ctx context.Context) (int64, error) {
	var row struct {
		Value string `gorm:"column:value"`
	}
	query := fmt.Sprintf("SELECT value FROM %sconfiguration WHERE name = 'PS_ROOT_CATEGORY' LIMIT 1", s.prefix)
	if err := s.db.WithContext(ctx).Raw(query).Scan(&row).Error; err != nil {
		return 0, err
	}
	id := parseInt64(row.Value)
	if id == 0 {
		return 1, nil
	}
	return id, nil
}

func (s *Service) defaultCurrencyID(ctx context.Context) (int64, error) {
	var row struct {
		Value string `gorm:"column:value"`
	}
	query := fmt.Sprintf("SELECT value FROM %sconfiguration WHERE name = 'PS_CURRENCY_DEFAULT' LIMIT 1", s.prefix)
	if err := s.db.WithContext(ctx).Raw(query).Scan(&row).Error; err != nil {
		return 0, err
	}
	id := parseInt64(row.Value)
	if id == 0 {
		return 1, nil
	}
	return id, nil
}

func (s *Service) columnExists(ctx context.Context, tableName string, columnName string) (bool, error) {
	var count int64
	query := `
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
  AND table_name = ?
  AND column_name = ?
`
	if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(query), tableName, columnName).Scan(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func parseInt64(value string) int64 {
	var n int64
	for _, r := range value {
		if r < '0' || r > '9' {
			return 0
		}
		n = n*10 + int64(r-'0')
	}
	return n
}

func buildMenuTree(id int64, nodes map[int64]MenuItem, childrenByParent map[int64][]int64) (MenuItem, bool) {
	item, ok := nodes[id]
	if !ok {
		return MenuItem{}, false
	}
	childIDs := childrenByParent[id]
	if len(childIDs) == 0 {
		return item, true
	}
	item.Children = make([]MenuItem, 0, len(childIDs))
	for _, childID := range childIDs {
		child, ok := buildMenuTree(childID, nodes, childrenByParent)
		if ok {
			item.Children = append(item.Children, child)
		}
	}
	return item, true
}

func pickLocaleOptionByID(options []LocaleOption, id int64) LocaleOption {
	for _, option := range options {
		if option.ID == id && id != 0 {
			return option
		}
	}
	if len(options) > 0 {
		return options[0]
	}
	return LocaleOption{}
}

func pickLocaleOptionByCode(options []LocaleOption, code string) LocaleOption {
	code = strings.ToUpper(strings.TrimSpace(code))
	for _, option := range options {
		if option.Code == code && code != "" {
			return option
		}
	}
	if len(options) > 0 {
		return options[0]
	}
	if code == "" {
		return LocaleOption{}
	}
	return LocaleOption{Code: code, Label: code, Meta: code}
}
