package paste

import (
	"bufio"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"go.sour.is/paste/src/pkg/readutil"
	"golang.org/x/sys/unix"
	"sour.is/x/toolbox/log"
)

type Paste struct {
	store string
}

func New(store string) (p *Paste, err error) {
	p = &Paste{}
	p.store = store

	if !chkStore(p.store) {
		err = fmt.Errorf("paste: store location [%s] does not exist or is not writable", p.store)
		return
	}
	return
}

func chkStore(path string) bool {
	file, err := os.Stat(path)

	if err != nil && os.IsNotExist(err) {
		err = os.MkdirAll(path, 0744)
		if err != nil {
			return false
		}

		file, err = os.Stat(path)
	}

	if err != nil {
		return false
	}

	if !file.IsDir() {
		return false
	}

	if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
		return false
	}

	return true
}

func chkFile(path string) bool {
	file, err := os.Stat(path)
	if err != nil {
		return false
	}

	if file.IsDir() {
		return false
	}

	if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
		return false
	}

	return true
}

func chkGone(path string) bool {
	file, err := os.Stat(path)
	if err != nil {
		return true
	}

	if file.Size() == 0 {
		return true
	}

	return false
}

func (p *Paste) Get(w io.Writer, id string) error {
	fname := filepath.Join(p.store, id)

	if !chkFile(fname) {
		return ErrNotFound
	}

	if chkGone(fname) {
		return ErrGone
	}

	f, err := os.Open(fname)
	if err != nil {
		return fmt.Errorf("%w: %w", ErrReadingContent, err)
	}
	defer f.Close()

	pr := readutil.NewPreviewReader(f)

	keep := true
	scanner := bufio.NewScanner(pr)
	for scanner.Scan() {
		txt := scanner.Text()
		log.Debug(txt)

		// End of header found
		if txt == "" {
			break
		}

		if strings.HasPrefix(txt, "exp:") {
			now := time.Now().Unix()
			exp, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(txt, "exp:")), 10, 64)
			if err != nil {
				return fmt.Errorf("%w: %w", ErrReadingContent, err)
			}

			log.Debugf("%d > %d", now, exp)
			if now > exp {
				deleteFile(fname)
				return ErrGone
			}
		}

		if strings.HasPrefix(txt, "burn:") {
			burn := strings.TrimSpace(strings.TrimPrefix(txt, "burn:"))

			if burn == "true" {
				keep = false
			}
		}
	}

	_, _ = io.Copy(w, pr.Drain())

	if !keep {
		deleteFile(fname)
	}

	return nil
}

func (p *Paste) Set(r io.ReadCloser) (string, error) {
	defer r.Close()

	rdr := io.LimitReader(r, 1048576)

	s256 := sha256.New()
	tmp, _ := os.CreateTemp(p.store, "arch-")
	defer os.Remove(tmp.Name())

	m := io.MultiWriter(s256, tmp)
	if _, err := io.Copy(m, rdr); err != nil {
		return "", fmt.Errorf("%w: %w", ErrBadInput, err)
	}
	tmp.Close()

	id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
	fname := filepath.Join(p.store, id)

	_ = os.Rename(tmp.Name(), fname)

	return id, nil
}

func deleteFile(path string) {
	_ = ioutil.WriteFile(path, nil, 0644)
}

var (
	ErrNotFound       = errors.New("not found")
	ErrGone           = errors.New("gone")
	ErrReadingContent = errors.New("reading content")
	ErrBadInput       = errors.New("bad input")
)
