package common

import (
	"database/sql"
	"errors"

	//"fmt"
	"os"
	"path/filepath"
	"strings"

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

var Attachments AttachmentStore

type MiniAttachment struct {
	ID         int
	SectionID  int
	OriginID   int
	UploadedBy int
	Path       string
	Extra      string

	Image bool
	Ext   string
}

type Attachment struct {
	ID           int
	SectionTable string
	SectionID    int
	OriginTable  string
	OriginID     int
	UploadedBy   int
	Path         string
	Extra        string

	Image bool
	Ext   string
}

type AttachmentStore interface {
	FGet(id int) (*Attachment, error)
	Get(id int) (*MiniAttachment, error)
	MiniGetList(originTable string, originID int) (alist []*MiniAttachment, err error)
	BulkMiniGetList(originTable string, ids []int) (amap map[int][]*MiniAttachment, err error)
	Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path, extra string) (int, error)
	MoveTo(sectionID, originID int, originTable string) error
	MoveToByExtra(sectionID int, originTable, extra string) error
	Count() int
	CountIn(originTable string, oid int) int
	CountInPath(path string) int
	Delete(id int) error

	AddLinked(otable string, oid int) (err error)
	RemoveLinked(otable string, oid int) (err error)
}

type DefaultAttachmentStore struct {
	fget        *sql.Stmt
	get         *sql.Stmt
	getByObj    *sql.Stmt
	add         *sql.Stmt
	count       *sql.Stmt
	countIn     *sql.Stmt
	countInPath *sql.Stmt
	move        *sql.Stmt
	moveByExtra *sql.Stmt
	delete      *sql.Stmt

	replyUpdateAttachs *sql.Stmt
	topicUpdateAttachs *sql.Stmt
}

func NewDefaultAttachmentStore(acc *qgen.Accumulator) (*DefaultAttachmentStore, error) {
	a := "attachments"
	return &DefaultAttachmentStore{
		fget:        acc.Select(a).Columns("originTable, originID, sectionTable, sectionID, uploadedBy, path, extra").Where("attachID=?").Prepare(),
		get:         acc.Select(a).Columns("originID, sectionID, uploadedBy, path, extra").Where("attachID=?").Prepare(),
		getByObj:    acc.Select(a).Columns("attachID, sectionID, uploadedBy, path, extra").Where("originTable=? AND originID=?").Prepare(),
		add:         acc.Insert(a).Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path, extra").Fields("?,?,?,?,?,?,?").Prepare(),
		count:       acc.Count(a).Prepare(),
		countIn:     acc.Count(a).Where("originTable=? and originID=?").Prepare(),
		countInPath: acc.Count(a).Where("path=?").Prepare(),
		move:        acc.Update(a).Set("sectionID=?").Where("originID=? AND originTable=?").Prepare(),
		moveByExtra: acc.Update(a).Set("sectionID=?").Where("originTable=? AND extra=?").Prepare(),
		delete:      acc.Delete(a).Where("attachID=?").Prepare(),

		// TODO: Less race-y attachment count updates
		replyUpdateAttachs: acc.Update("replies").Set("attachCount=?").Where("rid=?").Prepare(),
		topicUpdateAttachs: acc.Update("topics").Set("attachCount=?").Where("tid=?").Prepare(),
	}, acc.FirstError()
}

func (s *DefaultAttachmentStore) MiniGetList(originTable string, originID int) (alist []*MiniAttachment, err error) {
	rows, err := s.getByObj.Query(originTable, originID)
	defer rows.Close()
	for rows.Next() {
		a := &MiniAttachment{OriginID: originID}
		err := rows.Scan(&a.ID, &a.SectionID, &a.UploadedBy, &a.Path, &a.Extra)
		if err != nil {
			return nil, err
		}
		a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".")
		if len(a.Ext) == 0 {
			return nil, errors.New("corrupt attachment path")
		}
		a.Image = ImageFileExts.Contains(a.Ext)
		alist = append(alist, a)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}
	if len(alist) == 0 {
		err = sql.ErrNoRows
	}
	return alist, err
}

func (s *DefaultAttachmentStore) BulkMiniGetList(originTable string, ids []int) (amap map[int][]*MiniAttachment, err error) {
	if len(ids) == 0 {
		return nil, sql.ErrNoRows
	}
	if len(ids) == 1 {
		res, err := s.MiniGetList(originTable, ids[0])
		return map[int][]*MiniAttachment{ids[0]: res}, err
	}

	amap = make(map[int][]*MiniAttachment)
	var buffer []*MiniAttachment
	var currentID int
	rows, err := qgen.NewAcc().Select("attachments").Columns("attachID,sectionID,originID,uploadedBy,path").Where("originTable=?").In("originID", ids).Orderby("originID ASC").Query(originTable)
	defer rows.Close()
	for rows.Next() {
		a := &MiniAttachment{}
		err := rows.Scan(&a.ID, &a.SectionID, &a.OriginID, &a.UploadedBy, &a.Path)
		if err != nil {
			return nil, err
		}
		a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".")
		if len(a.Ext) == 0 {
			return nil, errors.New("corrupt attachment path")
		}
		a.Image = ImageFileExts.Contains(a.Ext)
		if currentID == 0 {
			currentID = a.OriginID
		}
		if a.OriginID != currentID {
			if len(buffer) > 0 {
				amap[currentID] = buffer
				currentID = a.OriginID
				buffer = nil
			}
		}
		buffer = append(buffer, a)
	}
	if len(buffer) > 0 {
		amap[currentID] = buffer
	}
	return amap, rows.Err()
}

func (s *DefaultAttachmentStore) FGet(id int) (*Attachment, error) {
	a := &Attachment{ID: id}
	err := s.fget.QueryRow(id).Scan(&a.OriginTable, &a.OriginID, &a.SectionTable, &a.SectionID, &a.UploadedBy, &a.Path, &a.Extra)
	if err != nil {
		return nil, err
	}
	a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".")
	if len(a.Ext) == 0 {
		return nil, errors.New("corrupt attachment path")
	}
	a.Image = ImageFileExts.Contains(a.Ext)
	return a, nil
}

func (s *DefaultAttachmentStore) Get(id int) (*MiniAttachment, error) {
	a := &MiniAttachment{ID: id}
	err := s.get.QueryRow(id).Scan(&a.OriginID, &a.SectionID, &a.UploadedBy, &a.Path, &a.Extra)
	if err != nil {
		return nil, err
	}
	a.Ext = strings.TrimPrefix(filepath.Ext(a.Path), ".")
	if len(a.Ext) == 0 {
		return nil, errors.New("corrupt attachment path")
	}
	a.Image = ImageFileExts.Contains(a.Ext)
	return a, nil
}

func (s *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path, extra string) (int, error) {
	res, err := s.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path, extra)
	if err != nil {
		return 0, err
	}
	lid, err := res.LastInsertId()
	return int(lid), err
}

func (s *DefaultAttachmentStore) MoveTo(sectionID, originID int, originTable string) error {
	_, err := s.move.Exec(sectionID, originID, originTable)
	return err
}

func (s *DefaultAttachmentStore) MoveToByExtra(sectionID int, originTable, extra string) error {
	_, err := s.moveByExtra.Exec(sectionID, originTable, extra)
	return err
}

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

func (s *DefaultAttachmentStore) CountIn(originTable string, oid int) (count int) {
	err := s.countIn.QueryRow(originTable, oid).Scan(&count)
	if err != nil {
		LogError(err)
	}
	return count
}

func (s *DefaultAttachmentStore) CountInPath(path string) (count int) {
	err := s.countInPath.QueryRow(path).Scan(&count)
	if err != nil {
		LogError(err)
	}
	return count
}

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

// TODO: Split this out of this store
func (s *DefaultAttachmentStore) AddLinked(otable string, oid int) (err error) {
	switch otable {
	case "topics":
		_, err = s.topicUpdateAttachs.Exec(s.CountIn(otable, oid), oid)
		if err != nil {
			return err
		}
		err = Topics.Reload(oid)
	case "replies":
		_, err = s.replyUpdateAttachs.Exec(s.CountIn(otable, oid), oid)
		if err != nil {
			return err
		}
		err = Rstore.GetCache().Remove(oid)
	}
	if err == sql.ErrNoRows {
		err = nil
	}
	if err != nil {
		return err
	}
	return nil
}

// TODO: Split this out of this store
func (s *DefaultAttachmentStore) RemoveLinked(otable string, oid int) (err error) {
	switch otable {
	case "topics":
		_, err = s.topicUpdateAttachs.Exec(s.CountIn(otable, oid), oid)
		if err != nil {
			return err
		}
		if tc := Topics.GetCache(); tc != nil {
			tc.Remove(oid)
		}
	case "replies":
		_, err = s.replyUpdateAttachs.Exec(s.CountIn(otable, oid), oid)
		if err != nil {
			return err
		}
		err = Rstore.GetCache().Remove(oid)
	}
	if err != nil {
		return err
	}
	return nil
}

// TODO: Add a table for the files and lock the file row when performing tasks related to the file
func DeleteAttachment(aid int) error {
	a, err := Attachments.FGet(aid)
	if err != nil {
		return err
	}
	err = deleteAttachment(a)
	if err != nil {
		return err
	}
	_ = Attachments.RemoveLinked(a.OriginTable, a.OriginID)
	return nil
}

func deleteAttachment(a *Attachment) error {
	err := Attachments.Delete(a.ID)
	if err != nil {
		return err
	}

	count := Attachments.CountInPath(a.Path)
	if count == 0 {
		err := os.Remove("./attachs/" + a.Path)
		if err != nil {
			return err
		}
	}

	return nil
}