package models

import (
	"bytes"
	"database/sql"
	"errors"
	"strconv"
	"strings"

	"brainminder.speedtech.it/internal/database"
)

type Item struct {
	Id                  int64  `db:"id"`
	Title               string `db:"title"`
	Summary             string `db:"summary"`
	SummaryRendered     string `db:"summary_rendered"`
	Description         string `db:"description"`
	DescriptionRendered string `db:"description_rendered"`
	Active              int    `db:"active"`
	Tags                string `db:"tags"`
	TypeId              int64  `db:"type_id"`
	Categories          string `db:"categories"`
	Notebooks           string `db:"notebooks"`
	Crypted             int    `db:"crypted"`
	Hidden              int    `db:"hidden"`
	OnDashboard         int    `db:"on_dashboard"`
	OnDashboardPosition int    `db:"on_dashboard_position"`
	TypeTitle           string `db:"type_title"`
	TypeIcon            string `db:"type_icon"`
	TypeShowSummary     int    `db:"type_show_summary"`
	TypeShowDescription int    `db:"type_show_description"`
	FieldsOnList        []Field
	FieldsValues        []FieldValue
	FieldsValuesMap     map[int64]map[int]string
}

type ItemRelation struct {
	ItemId        int64  `db:"item_id"`
	RelatedItemId int64  `db:"related_item_id"`
	RelationType  string `db:"relation_type"`
	Title         string `db:"title"`
	Categories    string `db:"categories"`
	Tags          string `db:"tags"`
	TypeTitle     string `db:"type_title"`
	TypeIcon      string `db:"type_icon"`
}

type ItemModel struct {
	*BaseModel
}

func (model *ItemModel) One(id int64) (*Item, bool, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	var row Item

	query := `SELECT bmi.*, bmt.title AS type_title, bmt.icon AS type_icon, bmt.show_summary AS type_show_summary, 
	bmt.show_description AS type_show_description FROM bm_item bmi 
	INNER JOIN bm_type bmt ON bmi.type_id=bmt.id WHERE bmi.id = $1`

	err := model.DB.GetContext(ctx, &row, query, id)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, false, nil
	}

	return &row, true, err
}

func (model *ItemModel) Delete(id int) (bool, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	_, err := model.DB.ExecContext(ctx, `DELETE FROM bm_item WHERE id = $1`, id)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}

	_, err = model.DB.ExecContext(ctx, `DELETE FROM bm_item_relations WHERE id = $1 OR related_item_id=$1`, id)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}

	_, err = model.DB.ExecContext(ctx, `DELETE FROM bm_item_fields WHERE item_id = $1`, id)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}

	_, err = model.DB.ExecContext(ctx, `DELETE FROM bm_item_keywords WHERE item_id = $1`, id)
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}

	return true, err
}

func (model *ItemModel) Search(searchText string, criteria map[string]any) ([]Item, bool, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	var params []interface{}
	var conditions []string
	var cond string

	var conditionsCriteria []string
	var condCriteria string

	query := `SELECT DISTINCT bmi.*, bmt.title AS type_title, bmt.icon AS type_icon FROM bm_item bmi 
	INNER JOIN bm_type bmt ON bmi.type_id=bmt.id 
	INNER JOIN bm_item_keywords bit ON bit.item_id=bmi.id	`

	for field, value := range criteria {
		switch field {
		case "notebook_id":
			if value != nil {
				valint := value.(int64)
				if valint > 0 {
					valstr := "|" + strconv.FormatInt(valint, 10) + "|"
					params = append(params, valstr)
					conditionsCriteria = append(conditionsCriteria, "INSTR(bmi.notebooks, ?) > 0 ")
				}
			}
		}
	}

	if len(searchText) > 0 {
		params = append(params, strings.ToLower(searchText))
		conditions = append(conditions, "bit.keyword = ? ")
	}

	for _, s := range strings.Split(searchText, " ") {
		s = strings.ToLower(s)
		if len(s) > 0 {
			params = append(params, s)
			conditions = append(conditions, "bit.keyword = ? ")
		}
	}

	for _, condition := range conditions {
		if len(cond) > 0 {
			cond = cond + " OR "
		}
		cond = cond + condition
	}

	for _, condition := range conditionsCriteria {
		if len(condCriteria) > 0 {
			condCriteria = condCriteria + " AND "
		}
		condCriteria = condCriteria + condition
	}

	lenCond := len(cond)
	lenCondCriteria := len(condCriteria)

	if lenCondCriteria > 0 || lenCond > 0 {
		query = query + "WHERE "
	}

	if lenCondCriteria > 0 {
		query = query + condCriteria
		if lenCond > 0 {
			query = query + " AND (" + cond + ")"
		}
	} else {
		if lenCond > 0 {
			query = query + " (" + cond + ")"
		}
	}

	query = query + ` ORDER BY bmi.title`

	var rows []Item

	err := model.DB.SelectContext(ctx, &rows, query, params...)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, false, nil
	}

	return rows, true, err

}

func (model *ItemModel) Find(criteria map[string]any, offset int64) ([]Item, bool, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	var params []interface{}
	var conditions []string
	var cond string
	query := `SELECT DISTINCT bmi.*, bmt.title AS type_title, bmt.icon AS type_icon FROM bm_item bmi 
	INNER JOIN bm_type bmt ON bmi.type_id=bmt.id `

	for field, value := range criteria {
		switch field {
		case "Title":
			valstr := value.(string)
			if len(valstr) > 0 {
				params = append(params, valstr)
				conditions = append(conditions, "bmi.title LIKE '%' || ? || '%'")
			}
		case "Tags":
			valstr := value.(string)
			if len(valstr) > 0 {
				params = append(params, valstr)
				conditions = append(conditions, "bmi.tags LIKE '%' || ? || '%'")
			}
		case "type_id":
			valint := value.(int64)
			if valint > 0 {
				params = append(params, valint)
				conditions = append(conditions, "bmi.type_id=?")
			}
		case "notebook_id":
			if value != nil {
				valint := value.(int64)
				if valint > 0 {
					valstr := "|" + strconv.FormatInt(valint, 10) + "|"
					params = append(params, valstr)
					conditions = append(conditions, "INSTR(bmi.notebooks, ?) > 0")
				}
			}
		case "category_id":
			if value != nil {
				valint := value.(int64)
				if valint > 0 {
					valstr := "|" + strconv.FormatInt(valint, 10) + "|"
					params = append(params, valstr)
					conditions = append(conditions, "INSTR(bmi.categories, ?) > 0")
				}
			}
		case "On_dashboard":
			valint := value.(int)
			if valint > 0 {
				params = append(params, valint)
				conditions = append(conditions, "bmi.on_dashboard=?")
			}
		}
	}

	for _, condition := range conditions {
		if len(cond) > 0 {
			cond = cond + " AND "
		}
		cond = cond + condition
	}

	if len(cond) > 0 {
		query = query + "WHERE " + cond + " "
	}

	query = query + ` ORDER BY bmi.title`

	params = append(params, offset)
	query = query + ` LIMIT 10 OFFSET ?`

	var rows []Item

	err := model.DB.SelectContext(ctx, &rows, query, params...)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, false, nil
	}

	return rows, true, err
}

func (model *ItemModel) Create(Item *Item) (int64, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	markdown := *model.GetMarkdown()

	var bufSummary bytes.Buffer
	markdown.Convert([]byte(Item.Summary), &bufSummary)
	Item.SummaryRendered = bufSummary.String()

	var bufDescription bytes.Buffer
	markdown.Convert([]byte(Item.Description), &bufDescription)
	Item.DescriptionRendered = bufDescription.String()

	query := `INSERT INTO bm_item (type_id, title, summary, summary_rendered, description, description_rendered, on_dashboard, tags, notebooks, categories) 
	VALUES (:type_id, :title, :summary, :summary_rendered, :description, :description_rendered, :on_dashboard, :tags, :notebooks, :categories)`

	result, err := model.DB.NamedExecContext(ctx, query, Item)
	if err != nil {
		return 0, err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return 0, err
	}

	return id, err
}

func (model *ItemModel) Update(Item *Item) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	markdown := *model.GetMarkdown()

	var bufSummary bytes.Buffer
	markdown.Convert([]byte(Item.Summary), &bufSummary)
	Item.SummaryRendered = bufSummary.String()

	var bufDescription bytes.Buffer
	markdown.Convert([]byte(Item.Description), &bufDescription)
	Item.DescriptionRendered = bufDescription.String()

	query := `UPDATE bm_item SET title=:title, type_id=:type_id, summary=:summary, summary_rendered=:summary_rendered, 
	description=:description, description_rendered=:description_rendered, tags=:tags, on_dashboard=:on_dashboard, 
	categories=:categories, notebooks =:notebooks WHERE id = :id`
	_, err := model.DB.NamedExecContext(ctx, query, Item)

	if err != nil {
		return err
	}

	return err
}

func (model *ItemModel) AddToDashboard(id int64) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `UPDATE bm_item SET on_dashboard=1 WHERE id = :id`
	_, err := model.DB.ExecContext(ctx, query, id)

	if err != nil {
		return err
	}

	return err
}

func (model *ItemModel) RemoveFromDashboard(id int64) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `UPDATE bm_item SET on_dashboard=0 WHERE id = :id`
	_, err := model.DB.ExecContext(ctx, query, id)

	if err != nil {
		return err
	}

	return err
}

func (model *ItemModel) SaveKeywords(Item *Item, fields *[]Field, fieldsValues map[int64]map[int]string) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	var keywords []string
	keywords = append(keywords, Item.TypeTitle)

	var categoriesInt []int64
	categoriesStr := strings.Split(strings.Trim(Item.Categories, "|"), "|")
	for _, categoryStr := range categoriesStr {
		categoryInt, _ := strconv.ParseInt(categoryStr, 10, 64)
		categoriesInt = append(categoriesInt, categoryInt)
	}

	categoryModel := &CategoryModel{DB: model.DB}
	categories, _, _ := categoryModel.Find(categoriesInt)
	for _, category := range categories {
		keywords = append(keywords, category.Name)
	}

	keywords = append(keywords, strings.Split(Item.Tags, ",")...)
	keywords = append(keywords, strings.Split(Item.Title, " ")...)

	for _, field := range *fields {
		if field.Widget != "url" {
			values, found := fieldsValues[field.TypeFieldId]
			if found {
				for _, value := range values {
					keywords = append(keywords, value)
				}
			}
		}
	}

	query := `DELETE FROM bm_item_keywords WHERE item_id = $1`
	_, err := model.DB.ExecContext(ctx, query, Item.Id)

	if err != nil {
		return err
	}

	stmt, err := model.DB.Prepare(`INSERT INTO bm_item_keywords (item_id, keyword) VALUES($1, $2)`)
	for _, keyword := range keywords {
		keyword = strings.ToLower(strings.TrimSpace(keyword))
		if len(keyword) > 1 {
			_, err = stmt.ExecContext(ctx, Item.Id, keyword)
		}

		if err != nil {
			return err
		}
	}

	return nil
}

func (model *ItemModel) AddRelation(id int64, relatedId int64, relationType string) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `INSERT INTO bm_item_relations (itemId, relatedItemId, relationType) VALUES($1, $2, $3)`
	_, err := model.DB.ExecContext(ctx, query, id, relatedId, relationType)

	if err != nil {
		return err
	}
	return nil
}

func (model *ItemModel) UpdateRelation(id int64, relatedId int64, relationType string) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `UPDATE bm_item_relations SET relation_type=$1 WHERE item_id=$2 AND related_item_id=$3`
	_, err := model.DB.ExecContext(ctx, query, relationType, id, relatedId)

	if err != nil {
		return err
	}
	return nil
}

func (model *ItemModel) DeleteRelation(id int64, relatedId int64) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `DELETE FROM bm_item_relations WHERE item_id=$1 AND related_item_id=$2`
	_, err := model.DB.ExecContext(ctx, query, id, relatedId)

	if err != nil {
		return err
	}
	return nil
}

func (model *ItemModel) DeleteShare(shareId int64) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `DELETE FROM bm_item_shares WHERE id=$1`
	_, err := model.DB.ExecContext(ctx, query, shareId)

	if err != nil {
		return err
	}
	return nil
}

func (model *ItemModel) GetRelations(id int64) ([]ItemRelation, bool, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `SELECT bir.item_id, bir.related_item_id, bmi.title, bmi.categories, bmi.tags, bmt.title AS type_title, 
	bmt.icon AS type_icon, bir.relation_type FROM bm_item bmi 
	INNER JOIN bm_type bmt ON bmi.type_id=bmt.id 
	INNER JOIN bm_item_relations bir ON bir.related_item_id=bmi.id WHERE bir.item_id=$1
	UNION
	SELECT bir.item_id, bir.related_item_id, bmi.title, bmi.categories, bmi.tags, bmt.title AS type_title, 
	bmt.icon AS type_icon, bir.relation_type FROM bm_item bmi 
	INNER JOIN bm_type bmt ON bmi.type_id=bmt.id 
	INNER JOIN bm_item_relations bir ON bir.item_id=bmi.id WHERE bir.related_item_id=$1
	`

	var rows []ItemRelation

	err := model.DB.SelectContext(ctx, &rows, query, id)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, false, nil
	}

	return rows, true, err
}

func (model *ItemModel) GetShares(id int64) ([]ItemShare, bool, error) {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `SELECT * FROM bm_item_shares WHERE item_id=$1`

	var rows []ItemShare

	err := model.DB.SelectContext(ctx, &rows, query, id)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, false, nil
	}

	return rows, true, err
}
