package fetdata

import (
	"math"
	"sort"
	"strings"

	"golang-fave/engine/utils"
	"golang-fave/engine/wrapper"
)

type ShopPagination struct {
	Num     string
	Link    string
	Current bool
	Dots    bool
}

type Shop struct {
	wrap     *wrapper.Wrapper
	category *ShopCategory
	product  *ShopProduct

	products         []*ShopProduct
	productsCount    int
	productsPerPage  int
	productsMaxPage  int
	productsCurrPage int
	pagination       []*ShopPagination
	paginationPrev   *ShopPagination
	paginationNext   *ShopPagination

	bufferCats map[int]*utils.MySql_shop_category
}

func (this *Shop) load() *Shop {
	if this == nil {
		return this
	}
	if (*this.wrap.Config).Modules.Enabled.Shop == 0 {
		return this
	}
	sql_nums := `
		SELECT
			COUNT(*)
		FROM
			fave_shop_products
		WHERE
			active = 1 AND
			parent_id IS NULL
		;
	`
	sql_rows := `
		SELECT
			fave_shop_products.id,
			fave_shop_products.user,
			fave_shop_products.currency,
			fave_shop_products.price,
			fave_shop_products.price_old,
			fave_shop_products.gname,
			fave_shop_products.name,
			fave_shop_products.alias,
			fave_shop_products.vendor,
			fave_shop_products.quantity,
			fave_shop_products.category,
			fave_shop_products.briefly,
			fave_shop_products.content,
			UNIX_TIMESTAMP(fave_shop_products.datetime) as datetime,
			fave_shop_products.active,
			fave_users.id,
			fave_users.first_name,
			fave_users.last_name,
			fave_users.email,
			fave_users.admin,
			fave_users.active,
			fave_shop_currencies.id,
			fave_shop_currencies.name,
			fave_shop_currencies.coefficient,
			fave_shop_currencies.code,
			fave_shop_currencies.symbol,
			cats.id,
			cats.user,
			cats.name,
			cats.alias,
			cats.lft,
			cats.rgt,
			cats.depth,
			cats.parent_id
		FROM
			fave_shop_products
			LEFT JOIN fave_users ON fave_users.id = fave_shop_products.user
			LEFT JOIN fave_shop_currencies ON fave_shop_currencies.id = fave_shop_products.currency
			LEFT JOIN (
				SELECT
					main.id,
					main.user,
					main.name,
					main.alias,
					main.lft,
					main.rgt,
					main.depth,
					parent.id AS parent_id
				FROM
					(
						SELECT
							node.id,
							node.user,
							node.name,
							node.alias,
							node.lft,
							node.rgt,
							(COUNT(parent.id) - 1) AS depth
						FROM
							fave_shop_cats AS node,
							fave_shop_cats AS parent
						WHERE
							node.lft BETWEEN parent.lft AND parent.rgt
						GROUP BY
							node.id
						ORDER BY
							node.lft ASC
					) AS main
					LEFT JOIN (
						SELECT
							node.id,
							node.user,
							node.name,
							node.alias,
							node.lft,
							node.rgt,
							(COUNT(parent.id) - 0) AS depth
						FROM
							fave_shop_cats AS node,
							fave_shop_cats AS parent
						WHERE
							node.lft BETWEEN parent.lft AND parent.rgt
						GROUP BY
							node.id
						ORDER BY
							node.lft ASC
					) AS parent ON
					parent.depth = main.depth AND
					main.lft > parent.lft AND
					main.rgt < parent.rgt
				WHERE
					main.id > 1
				ORDER BY
					main.lft ASC
			) AS cats ON cats.id = fave_shop_products.category
		WHERE
			fave_shop_products.active = 1 AND
			fave_shop_products.parent_id IS NULL
		ORDER BY
			fave_shop_products.quantity DESC,
			fave_shop_products.id DESC
		LIMIT ?, ?;
	`

	// Category selected
	if this.category != nil {
		var cat_ids []string
		if rows, err := this.wrap.DB.Query(
			this.wrap.R.Context(),
			`SELECT
				node.id
			FROM
				fave_shop_cats AS node,
				fave_shop_cats AS parent
			WHERE
				node.lft BETWEEN parent.lft AND parent.rgt AND
				node.id > 1 AND
				parent.id = ?
			GROUP BY
				node.id
			ORDER BY
				node.lft ASC
			;`,
			this.category.Id(),
		); err == nil {
			defer rows.Close()
			for rows.Next() {
				var cat_id string
				if err := rows.Scan(&cat_id); *this.wrap.LogCpError(&err) == nil {
					cat_ids = append(cat_ids, cat_id)
				}
			}
		}
		sql_nums = `
			SELECT
				COUNT(*)
			FROM
				(
					SELECT
						COUNT(*)
					FROM
						fave_shop_products
						LEFT JOIN fave_shop_cat_product_rel ON fave_shop_cat_product_rel.product_id = fave_shop_products.id
					WHERE
						fave_shop_products.active = 1 AND
						fave_shop_products.parent_id IS NULL AND
						fave_shop_cat_product_rel.category_id IN (` + strings.Join(cat_ids, ", ") + `)
					GROUP BY
						fave_shop_products.id
				) AS tbl
			;
		`
		sql_rows = `
			SELECT
				fave_shop_products.id,
				fave_shop_products.user,
				fave_shop_products.currency,
				fave_shop_products.price,
				fave_shop_products.price_old,
				fave_shop_products.gname,
				fave_shop_products.name,
				fave_shop_products.alias,
				fave_shop_products.vendor,
				fave_shop_products.quantity,
				fave_shop_products.category,
				fave_shop_products.briefly,
				fave_shop_products.content,
				UNIX_TIMESTAMP(fave_shop_products.datetime) AS datetime,
				fave_shop_products.active,
				fave_users.id,
				fave_users.first_name,
				fave_users.last_name,
				fave_users.email,
				fave_users.admin,
				fave_users.active,
				fave_shop_currencies.id,
				fave_shop_currencies.name,
				fave_shop_currencies.coefficient,
				fave_shop_currencies.code,
				fave_shop_currencies.symbol,
				cats.id,
				cats.user,
				cats.name,
				cats.alias,
				cats.lft,
				cats.rgt,
				cats.depth,
				cats.parent_id
			FROM
				fave_shop_products
				LEFT JOIN fave_shop_cat_product_rel ON fave_shop_cat_product_rel.product_id = fave_shop_products.id
				LEFT JOIN fave_users ON fave_users.id = fave_shop_products.user
				LEFT JOIN fave_shop_currencies ON fave_shop_currencies.id = fave_shop_products.currency
				LEFT JOIN (
					SELECT
						main.id,
						main.user,
						main.name,
						main.alias,
						main.lft,
						main.rgt,
						main.depth,
						parent.id AS parent_id
					FROM
						(
							SELECT
								node.id,
								node.user,
								node.name,
								node.alias,
								node.lft,
								node.rgt,
								(COUNT(parent.id) - 1) AS depth
							FROM
								fave_shop_cats AS node,
								fave_shop_cats AS parent
							WHERE
								node.lft BETWEEN parent.lft AND parent.rgt
							GROUP BY
								node.id
							ORDER BY
								node.lft ASC
						) AS main
						LEFT JOIN (
							SELECT
								node.id,
								node.user,
								node.name,
								node.alias,
								node.lft,
								node.rgt,
								(COUNT(parent.id) - 0) AS depth
							FROM
								fave_shop_cats AS node,
								fave_shop_cats AS parent
							WHERE
								node.lft BETWEEN parent.lft AND parent.rgt
							GROUP BY
								node.id
							ORDER BY
								node.lft ASC
						) AS parent ON
						parent.depth = main.depth AND
						main.lft > parent.lft AND
						main.rgt < parent.rgt
					WHERE
						main.id > 1
					ORDER BY
						main.lft ASC
				) AS cats ON cats.id = fave_shop_products.category
			WHERE
				fave_shop_products.active = 1 AND
				fave_shop_products.parent_id IS NULL AND
				fave_shop_cat_product_rel.category_id IN (` + strings.Join(cat_ids, ", ") + `)
			GROUP BY
				fave_shop_products.id,
				cats.parent_id
			ORDER BY
				fave_shop_products.quantity DESC,
				fave_shop_products.id DESC
			LIMIT ?, ?;
		`
	}

	product_ids := []string{}

	if err := this.wrap.DB.QueryRow(this.wrap.R.Context(), sql_nums).Scan(&this.productsCount); *this.wrap.LogCpError(&err) == nil {
		if this.category == nil {
			this.productsPerPage = (*this.wrap.Config).Shop.Pagination.Index
		} else {
			this.productsPerPage = (*this.wrap.Config).Shop.Pagination.Category
		}
		this.productsMaxPage = int(math.Ceil(float64(this.productsCount) / float64(this.productsPerPage)))
		this.productsCurrPage = this.wrap.GetCurrentPage(this.productsMaxPage)
		offset := this.productsCurrPage*this.productsPerPage - this.productsPerPage
		if rows, err := this.wrap.DB.Query(this.wrap.R.Context(), sql_rows, offset, this.productsPerPage); err == nil {
			defer rows.Close()
			for rows.Next() {
				rp := utils.MySql_shop_product{}
				ru := utils.MySql_user{}
				rc := utils.MySql_shop_currency{}
				ro := utils.MySql_shop_category{}
				if err := rows.Scan(
					&rp.A_id,
					&rp.A_user,
					&rp.A_currency,
					&rp.A_price,
					&rp.A_price_old,
					&rp.A_gname,
					&rp.A_name,
					&rp.A_alias,
					&rp.A_vendor,
					&rp.A_quantity,
					&rp.A_category,
					&rp.A_briefly,
					&rp.A_content,
					&rp.A_datetime,
					&rp.A_active,
					&ru.A_id,
					&ru.A_first_name,
					&ru.A_last_name,
					&ru.A_email,
					&ru.A_admin,
					&ru.A_active,
					&rc.A_id,
					&rc.A_name,
					&rc.A_coefficient,
					&rc.A_code,
					&rc.A_symbol,
					&ro.A_id,
					&ro.A_user,
					&ro.A_name,
					&ro.A_alias,
					&ro.A_lft,
					&ro.A_rgt,
					&ro.A_depth,
					&ro.A_parent,
				); *this.wrap.LogCpError(&err) == nil {
					product_ids = append(product_ids, utils.IntToStr(rp.A_id))
					this.products = append(this.products, &ShopProduct{
						wrap:     this.wrap,
						object:   &rp,
						user:     &User{wrap: this.wrap, object: &ru},
						currency: &ShopCurrency{wrap: this.wrap, object: &rc},
						category: &ShopCategory{wrap: this.wrap, object: &ro},
					})
				}
			}
		}
	}

	// Product images
	product_images := map[int][]*ShopProductImage{}
	if len(product_ids) > 0 {
		if rows, err := this.wrap.DB.Query(
			this.wrap.R.Context(),
			`SELECT
				fave_shop_product_images.product_id,
				fave_shop_product_images.filename
			FROM
				fave_shop_product_images
			WHERE
				fave_shop_product_images.product_id IN (`+strings.Join(product_ids, ", ")+`)
			ORDER BY
				fave_shop_product_images.ord ASC
			;`,
		); err == nil {
			defer rows.Close()
			for rows.Next() {
				img := utils.MySql_shop_product_image{}
				if err := rows.Scan(
					&img.A_product_id,
					&img.A_filename,
				); *this.wrap.LogCpError(&err) == nil {
					product_images[img.A_product_id] = append(product_images[img.A_product_id], &ShopProductImage{wrap: this.wrap, object: &img})
				}
			}
		}
	}
	for index, product := range this.products {
		if pimgs, ok := product_images[product.Id()]; ok {
			this.products[index].images = pimgs
		}
	}

	// Build pagination
	if true {
		for i := 1; i < this.productsCurrPage; i++ {
			if this.productsCurrPage >= 5 && i > 1 && i < this.productsCurrPage-1 {
				continue
			}
			if this.productsCurrPage >= 5 && i > 1 && i < this.productsCurrPage {
				this.pagination = append(this.pagination, &ShopPagination{
					Dots: true,
				})
			}
			link := this.wrap.R.URL.Path
			if i > 1 {
				link = link + "?p=" + utils.IntToStr(i)
			}
			this.pagination = append(this.pagination, &ShopPagination{
				Num:     utils.IntToStr(i),
				Link:    link,
				Current: false,
			})
		}

		// Current page
		link := this.wrap.R.URL.Path
		if this.productsCurrPage > 1 {
			link = link + "?p=" + utils.IntToStr(this.productsCurrPage)
		}
		this.pagination = append(this.pagination, &ShopPagination{
			Num:     utils.IntToStr(this.productsCurrPage),
			Link:    link,
			Current: true,
		})

		for i := this.productsCurrPage + 1; i <= this.productsMaxPage; i++ {
			if this.productsCurrPage < this.productsMaxPage-3 && i == this.productsCurrPage+3 {
				this.pagination = append(this.pagination, &ShopPagination{
					Dots: true,
				})
			}
			if this.productsCurrPage < this.productsMaxPage-3 && i > this.productsCurrPage+1 && i <= this.productsMaxPage-1 {
				continue
			}
			link := this.wrap.R.URL.Path
			if i > 1 {
				link = link + "?p=" + utils.IntToStr(i)
			}
			this.pagination = append(this.pagination, &ShopPagination{
				Num:     utils.IntToStr(i),
				Link:    link,
				Current: false,
			})
		}
	} else {
		for i := 1; i <= this.productsMaxPage; i++ {
			link := this.wrap.R.URL.Path
			if i > 1 {
				link = link + "?p=" + utils.IntToStr(i)
			}
			this.pagination = append(this.pagination, &ShopPagination{
				Num:     utils.IntToStr(i),
				Link:    link,
				Current: i == this.productsCurrPage,
			})
		}
	}

	// Pagination prev/next
	if this.productsMaxPage > 1 {
		link := this.wrap.R.URL.Path
		if this.productsCurrPage-1 > 1 {
			link = this.wrap.R.URL.Path + "?p=" + utils.IntToStr(this.productsCurrPage-1)
		}
		this.paginationPrev = &ShopPagination{
			Num:     utils.IntToStr(this.productsCurrPage - 1),
			Link:    link,
			Current: this.productsCurrPage <= 1,
		}
		if this.productsCurrPage >= 1 && this.productsCurrPage < this.productsMaxPage {
			link = this.wrap.R.URL.Path + "?p=" + utils.IntToStr(this.productsCurrPage+1)
		} else {
			link = this.wrap.R.URL.Path + "?p=" + utils.IntToStr(this.productsMaxPage)
		}
		this.paginationNext = &ShopPagination{
			Num:     utils.IntToStr(this.productsCurrPage + 1),
			Link:    link,
			Current: this.productsCurrPage >= this.productsMaxPage,
		}
	}

	return this
}

func (this *Shop) preload_cats() {
	if (*this.wrap.Config).Modules.Enabled.Shop == 0 {
		return
	}
	if this.bufferCats == nil {
		this.bufferCats = map[int]*utils.MySql_shop_category{}
		if rows, err := this.wrap.DB.Query(
			this.wrap.R.Context(),
			`SELECT
				main.id,
				main.user,
				main.name,
				main.alias,
				main.lft,
				main.rgt,
				main.depth,
				parent.id AS parent_id
			FROM
				(
					SELECT
						node.id,
						node.user,
						node.name,
						node.alias,
						node.lft,
						node.rgt,
						(COUNT(parent.id) - 1) AS depth
					FROM
						fave_shop_cats AS node,
						fave_shop_cats AS parent
					WHERE
						node.lft BETWEEN parent.lft AND parent.rgt
					GROUP BY
						node.id
					ORDER BY
						node.lft ASC
				) AS main
				LEFT JOIN (
					SELECT
						node.id,
						node.user,
						node.name,
						node.alias,
						node.lft,
						node.rgt,
						(COUNT(parent.id) - 0) AS depth
					FROM
						fave_shop_cats AS node,
						fave_shop_cats AS parent
					WHERE
						node.lft BETWEEN parent.lft AND parent.rgt
					GROUP BY
						node.id
					ORDER BY
						node.lft ASC
				) AS parent ON
				parent.depth = main.depth AND
				main.lft > parent.lft AND
				main.rgt < parent.rgt
			WHERE
				main.id > 1
			ORDER BY
				main.lft ASC
			;
		`); err == nil {
			defer rows.Close()
			for rows.Next() {
				row := utils.MySql_shop_category{}
				if err := rows.Scan(
					&row.A_id,
					&row.A_user,
					&row.A_name,
					&row.A_alias,
					&row.A_lft,
					&row.A_rgt,
					&row.A_depth,
					&row.A_parent,
				); *this.wrap.LogCpError(&err) == nil {
					this.bufferCats[row.A_id] = &row
					if _, ok := this.bufferCats[row.A_parent]; ok {
						this.bufferCats[row.A_parent].A_childs = true
					}
				}
			}
		}
	}
}

func (this *Shop) Category() *ShopCategory {
	if this == nil {
		return nil
	}
	return this.category
}

func (this *Shop) Product() *ShopProduct {
	if this == nil {
		return nil
	}
	return this.product
}

func (this *Shop) HaveProducts() bool {
	if this == nil {
		return false
	}
	if len(this.products) <= 0 {
		return false
	}
	return true
}

func (this *Shop) Products() []*ShopProduct {
	if this == nil {
		return []*ShopProduct{}
	}
	return this.products
}

func (this *Shop) ProductsCount() int {
	if this == nil {
		return 0
	}
	return this.productsCount
}

func (this *Shop) ProductsPerPage() int {
	if this == nil {
		return 0
	}
	return this.productsPerPage
}

func (this *Shop) ProductsMaxPage() int {
	if this == nil {
		return 0
	}
	return this.productsMaxPage
}

func (this *Shop) ProductsCurrPage() int {
	if this == nil {
		return 0
	}
	return this.productsCurrPage
}

func (this *Shop) Pagination() []*ShopPagination {
	if this == nil {
		return []*ShopPagination{}
	}
	return this.pagination
}

func (this *Shop) PaginationPrev() *ShopPagination {
	if this == nil {
		return nil
	}
	return this.paginationPrev
}

func (this *Shop) PaginationNext() *ShopPagination {
	if this == nil {
		return nil
	}
	return this.paginationNext
}

func (this *Shop) Currencies() []*ShopCurrency {
	result := []*ShopCurrency{}

	if (*this.wrap.Config).Modules.Enabled.Shop != 0 {
		for _, currency := range *this.wrap.ShopGetAllCurrencies() {
			obj := currency
			result = append(result, (&ShopCurrency{wrap: this.wrap, object: &obj}).load())
		}

		sort.Slice(result, func(i, j int) bool { return result[i].Id() < result[j].Id() })
	}

	return result
}

func (this *Shop) CurrentCurrency() *ShopCurrency {
	obj := *this.wrap.ShopGetCurrentCurrency()
	return (&ShopCurrency{wrap: this.wrap, object: &obj}).load()
}

func (this *Shop) Categories(parent, depth int) []*ShopCategory {
	this.preload_cats()

	depth_tmp := 0
	result := []*ShopCategory{}

	if (*this.wrap.Config).Modules.Enabled.Shop != 0 {
		for _, cat := range this.bufferCats {
			if parent <= 1 {
				if depth <= 0 {
					result = append(result, (&ShopCategory{wrap: this.wrap, object: cat}).load(&this.bufferCats))
				} else {
					if cat.A_depth <= depth {
						result = append(result, (&ShopCategory{wrap: this.wrap, object: cat}).load(&this.bufferCats))
					}
				}
			} else {
				if cat.A_parent == parent {
					if depth_tmp == 0 {
						depth_tmp = cat.A_depth
					}
					if depth <= 0 {
						result = append(result, (&ShopCategory{wrap: this.wrap, object: cat}).load(&this.bufferCats))
					} else {
						if (cat.A_depth - depth_tmp + 1) <= depth {
							result = append(result, (&ShopCategory{wrap: this.wrap, object: cat}).load(&this.bufferCats))
						}
					}
				}
			}
		}

		sort.Slice(result, func(i, j int) bool { return result[i].Left() < result[j].Left() })
	}

	return result
}