package common

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

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

type CustomPageStmts struct {
	update *sql.Stmt
	create *sql.Stmt
}

var customPageStmts CustomPageStmts

func init() {
	DbInits.Add(func(acc *qgen.Accumulator) error {
		customPageStmts = CustomPageStmts{
			update: acc.Update("pages").Set("name=?,title=?,body=?,allowedGroups=?,menuID=?").Where("pid=?").Prepare(),
			create: acc.Insert("pages").Columns("name,title,body,allowedGroups,menuID").Fields("?,?,?,?,?").Prepare(),
		}
		return acc.FirstError()
	})
}

type CustomPage struct {
	ID            int
	Name          string // TODO: Let admins put pages in "virtual subdirectories"
	Title         string
	Body          string
	AllowedGroups []int
	MenuID        int
}

func BlankCustomPage() *CustomPage {
	return new(CustomPage)
}

func (p *CustomPage) AddAllowedGroup(gid int) {
	p.AllowedGroups = append(p.AllowedGroups, gid)
}

func (p *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) {
	for _, group := range p.AllowedGroups {
		rawAllowedGroups += strconv.Itoa(group) + ","
	}
	if len(rawAllowedGroups) > 0 {
		rawAllowedGroups = rawAllowedGroups[:len(rawAllowedGroups)-1]
	}
	return rawAllowedGroups
}

func (p *CustomPage) Commit() error {
	_, err := customPageStmts.update.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID, p.ID)
	Pages.Reload(p.ID)
	return err
}

func (p *CustomPage) Create() (int, error) {
	res, err := customPageStmts.create.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID)
	if err != nil {
		return 0, err
	}
	pid64, err := res.LastInsertId()
	return int(pid64), err
}

var Pages PageStore

// Holds the custom pages, but doesn't include the template pages in /pages/ which are a lot more flexible yet harder to use and which are too risky security-wise to make editable in the Control Panel
type PageStore interface {
	Count() (count int)
	Get(id int) (*CustomPage, error)
	GetByName(name string) (*CustomPage, error)
	GetOffset(offset, perPage int) (pages []*CustomPage, err error)
	Reload(id int) error
	Delete(id int) error
}

// TODO: Add a cache to this to save on the queries
type DefaultPageStore struct {
	get       *sql.Stmt
	getByName *sql.Stmt
	getOffset *sql.Stmt
	count     *sql.Stmt
	delete    *sql.Stmt
}

func NewDefaultPageStore(acc *qgen.Accumulator) (*DefaultPageStore, error) {
	pa := "pages"
	allCols := "pid, name, title, body, allowedGroups, menuID"
	return &DefaultPageStore{
		get:       acc.Select(pa).Columns("name, title, body, allowedGroups, menuID").Where("pid=?").Prepare(),
		getByName: acc.Select(pa).Columns(allCols).Where("name=?").Prepare(),
		getOffset: acc.Select(pa).Columns(allCols).Orderby("pid DESC").Limit("?,?").Prepare(),
		count:     acc.Count(pa).Prepare(),
		delete:    acc.Delete(pa).Where("pid=?").Prepare(),
	}, acc.FirstError()
}

func (s *DefaultPageStore) Count() (count int) {
	err := s.count.QueryRow().Scan(&count)
	if err != nil {
		LogError(err)
	}
	return count
}

func (s *DefaultPageStore) parseAllowedGroups(raw string, page *CustomPage) error {
	if raw == "" {
		return nil
	}
	for _, sgroup := range strings.Split(raw, ",") {
		group, err := strconv.Atoi(sgroup)
		if err != nil {
			return err
		}
		page.AddAllowedGroup(group)
	}
	return nil
}

func (s *DefaultPageStore) Get(id int) (*CustomPage, error) {
	p := &CustomPage{ID: id}
	rawAllowedGroups := ""
	err := s.get.QueryRow(id).Scan(&p.Name, &p.Title, &p.Body, &rawAllowedGroups, &p.MenuID)
	if err != nil {
		return nil, err
	}
	return p, s.parseAllowedGroups(rawAllowedGroups, p)
}

func (s *DefaultPageStore) GetByName(name string) (*CustomPage, error) {
	p := BlankCustomPage()
	rawAllowedGroups := ""
	err := s.getByName.QueryRow(name).Scan(&p.ID, &p.Name, &p.Title, &p.Body, &rawAllowedGroups, &p.MenuID)
	if err != nil {
		return nil, err
	}
	return p, s.parseAllowedGroups(rawAllowedGroups, p)
}

func (s *DefaultPageStore) GetOffset(offset, perPage int) (pages []*CustomPage, err error) {
	rows, err := s.getOffset.Query(offset, perPage)
	if err != nil {
		return pages, err
	}
	defer rows.Close()

	for rows.Next() {
		p := &CustomPage{ID: 0}
		rawAllowedGroups := ""
		err := rows.Scan(&p.ID, &p.Name, &p.Title, &p.Body, &rawAllowedGroups, &p.MenuID)
		if err != nil {
			return pages, err
		}
		err = s.parseAllowedGroups(rawAllowedGroups, p)
		if err != nil {
			return pages, err
		}
		pages = append(pages, p)
	}
	return pages, rows.Err()
}

// Always returns nil as there's currently no cache
func (s *DefaultPageStore) Reload(id int) error {
	return nil
}

func (s *DefaultPageStore) Delete(id int) error {
	_, err := s.delete.Exec(id)
	return err
}