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"`
	Summary_rendered      string `db:"summary_rendered"`
	Description           string `db:"description"`
	Description_rendered  string `db:"description_rendered"`
	Active                int    `db:"active"`
	Tags                  string `db:"tags"`
	Type_id               int64  `db:"type_id"`
	Categories            string `db:"categories"`
	Notebooks             string `db:"notebooks"`
	Crypted               int    `db:"crypted"`
	Hidden                int    `db:"hidden"`
	On_dashboard          int    `db:"on_dashboard"`
	On_dashboard_position int    `db:"on_dashboard_position"`
	Type_title            string `db:"type_title"`
	Type_icon             string `db:"type_icon"`
	Type_show_summary     int    `db:"type_show_summary"`
	Type_show_description int    `db:"type_show_description"`
	FieldsOnList          []Field
	FieldsValues          []FieldValue
	FieldsValuesMap       map[int64]map[int]string
}

type ItemRelation struct {
	Item_id         int64  `db:"item_id"`
	Related_item_id int64  `db:"related_item_id"`
	Relation_type   string `db:"relation_type"`
	Title           string `db:"title"`
	Categories      string `db:"categories"`
	Tags            string `db:"tags"`
	Type_title      string `db:"type_title"`
	Type_icon       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 conditions_criteria []string
	var cond_criteria 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)
					conditions_criteria = append(conditions_criteria, "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 conditions_criteria {
		if len(cond_criteria) > 0 {
			cond_criteria = cond_criteria + " AND "
		}
		cond_criteria = cond_criteria + condition
	}

	len_cond := len(cond)
	len_cond_criteria := len(cond_criteria)

	if len_cond_criteria > 0 || len_cond > 0 {
		query = query + "WHERE "
	}

	if len_cond_criteria > 0 {
		query = query + cond_criteria
		if len_cond > 0 {
			query = query + " AND (" + cond + ")"
		}
	} else {
		if len_cond > 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.Summary_rendered = bufSummary.String()

	var bufDescription bytes.Buffer
	markdown.Convert([]byte(Item.Description), &bufDescription)
	Item.Description_rendered = 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.Summary_rendered = bufSummary.String()

	var bufDescription bytes.Buffer
	markdown.Convert([]byte(Item.Description), &bufDescription)
	Item.Description_rendered = 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.Type_title)

	var categories_int []int64
	categories_str := strings.Split(strings.Trim(Item.Categories, "|"), "|")
	for _, category_str := range categories_str {
		category_int, _ := strconv.ParseInt(category_str, 10, 64)
		categories_int = append(categories_int, category_int)
	}

	categoryModel := &CategoryModel{DB: model.DB}
	categories, _, _ := categoryModel.Find(categories_int)
	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.Type_field_id]
			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, related_id int64, relation_type string) error {
	ctx, cancel := database.GetContext()
	defer cancel()

	query := `INSERT INTO bm_item_relations (item_id, related_item_id, relation_type) VALUES($1, $2, $3)`
	_, err := model.DB.ExecContext(ctx, query, id, related_id, relation_type)

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

func (model *ItemModel) UpdateRelation(id int64, related_id int64, relation_type 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, relation_type, id, related_id)

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

func (model *ItemModel) DeleteRelation(id int64, related_id 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, related_id)

	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
}
