package common

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

	qgen "github.com/Azareal/Gosora/query_gen"
)

var RepliesSearch Searcher

type Searcher interface {
	Query(q string, zones []int) ([]int, error)
}

// TODO: Implement this
// Note: This is slow compared to something like ElasticSearch and very limited
type SQLSearcher struct {
	queryReplies *sql.Stmt
	queryTopics  *sql.Stmt
	queryRepliesZone *sql.Stmt
	queryTopicsZone *sql.Stmt
	//queryZone    *sql.Stmt
	fuzzyZone *sql.Stmt
}

// TODO: Support things other than MySQL
// TODO: Use LIMIT?
func NewSQLSearcher(acc *qgen.Accumulator) (*SQLSearcher, error) {
	if acc.GetAdapter().GetName() != "mysql" {
		return nil, errors.New("SQLSearcher only supports MySQL at this time")
	}
	return &SQLSearcher{
		queryReplies: acc.RawPrepare("SELECT tid FROM replies WHERE MATCH(content) AGAINST (? IN BOOLEAN MODE)"),
		queryTopics:  acc.RawPrepare("SELECT tid FROM topics WHERE MATCH(title) AGAINST (? IN BOOLEAN MODE) OR MATCH(content) AGAINST (? IN BOOLEAN MODE)"),
		queryRepliesZone: acc.RawPrepare("SELECT tid FROM replies WHERE MATCH(content) AGAINST (? IN BOOLEAN MODE) AND tid=?"),
		queryTopicsZone:  acc.RawPrepare("SELECT tid FROM topics WHERE (MATCH(title) AGAINST (? IN BOOLEAN MODE) OR MATCH(content) AGAINST (? IN BOOLEAN MODE)) AND parentID=?"),
		//queryZone:    acc.RawPrepare("SELECT topics.tid FROM topics INNER JOIN replies ON topics.tid = replies.tid WHERE (topics.title=? OR (MATCH(topics.title) AGAINST (? IN BOOLEAN MODE) OR MATCH(topics.content) AGAINST (? IN BOOLEAN MODE) OR MATCH(replies.content) AGAINST (? IN BOOLEAN MODE)) OR topics.content=? OR replies.content=?) AND topics.parentID=?"),
		fuzzyZone:    acc.RawPrepare("SELECT topics.tid FROM topics INNER JOIN replies ON topics.tid = replies.tid WHERE (topics.title LIKE ? OR topics.content LIKE ? OR replies.content LIKE ?) AND topics.parentID=?"),
	}, acc.FirstError()
}

func (s *SQLSearcher) queryAll(q string) ([]int, error) {
	var ids []int
	run := func(stmt *sql.Stmt, q ...interface{}) error {
		rows, err := stmt.Query(q...)
		if err == sql.ErrNoRows {
			return nil
		} else if err != nil {
			return err
		}
		defer rows.Close()

		for rows.Next() {
			var id int
			err := rows.Scan(&id)
			if err != nil {
				return err
			}
			ids = append(ids, id)
		}
		return rows.Err()
	}

	err := run(s.queryReplies, q)
	if err != nil {
		return nil, err
	}
	err = run(s.queryTopics, q, q)
	if err != nil {
		return nil, err
	}
	if len(ids) == 0 {
		err = sql.ErrNoRows
	}
	return ids, err
}

func (s *SQLSearcher) Query(q string, zones []int) (ids []int, err error) {
	if len(zones) == 0 {
		return nil, nil
	}
	run := func(rows *sql.Rows, err error) error {
		/*if err == sql.ErrNoRows {
			return nil
		} else */if err != nil {
			return err
		}
		defer rows.Close()

		for rows.Next() {
			var id int
			err := rows.Scan(&id)
			if err != nil {
				return err
			}
			ids = append(ids, id)
		}
		return rows.Err()
	}

	if len(zones) == 1 {
		//err = run(s.queryZone.Query(q, q, q, q, q,q, zones[0]))
		err = run(s.queryRepliesZone.Query(q, zones[0]))
		if err != nil {
			return nil, err
		}
		err = run(s.queryTopicsZone.Query(q, q,zones[0]))
	} else {
		var zList string
		for _, zone := range zones {
			zList += strconv.Itoa(zone) + ","
		}
		zList = zList[:len(zList)-1]

		acc := qgen.NewAcc()
		/*stmt := acc.RawPrepare("SELECT topics.tid FROM topics INNER JOIN replies ON topics.tid = replies.tid WHERE (MATCH(topics.title) AGAINST (? IN BOOLEAN MODE) OR MATCH(topics.content) AGAINST (? IN BOOLEAN MODE) OR MATCH(replies.content) AGAINST (? IN BOOLEAN MODE) OR topics.title=? OR topics.content=? OR replies.content=?) AND topics.parentID IN(" + zList + ")")
		err = acc.FirstError()
		if err != nil {
			return nil, err
		}*/
		// TODO: Cache common IN counts
		stmt := acc.RawPrepare("SELECT tid FROM topics WHERE (MATCH(topics.title) AGAINST (? IN BOOLEAN MODE) OR MATCH(topics.content) AGAINST (? IN BOOLEAN MODE)) AND parentID IN(" + zList + ")")
		err = acc.FirstError()
		if err != nil {
			return nil, err
		}
		err = run(stmt.Query(q, q))
		if err != nil {
			return nil, err
		}
		stmt = acc.RawPrepare("SELECT tid FROM replies WHERE MATCH(replies.content) AGAINST (? IN BOOLEAN MODE) AND tid IN(" + zList + ")")
		err = acc.FirstError()
		if err != nil {
			return nil, err
		}
		err = run(stmt.Query(q))
		//err = run(stmt.Query(q, q, q, q, q, q))
	}
	if err != nil {
		return nil, err
	}
	if len(ids) == 0 {
		err = sql.ErrNoRows
	}
	return ids, err
}

// TODO: Implement this
type ElasticSearchSearcher struct {
}

func NewElasticSearchSearcher() (*ElasticSearchSearcher, error) {
	return &ElasticSearchSearcher{}, nil
}

func (s *ElasticSearchSearcher) Query(q string, zones []int) ([]int, error) {
	return nil, nil
}