package dovecot

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"log"

	"git.autistici.org/ai3/go-common/unix"
)

var (
	failResponse    = []byte{'F', '\n'}
	noMatchResponse = []byte{'N', '\n'}
)

// DictDatabase is an interface to a key/value store by way of the Lookup
// method.
type DictDatabase interface {
	// Lookup a key. The result boolean is the presence flag, to
	// avoid having to check the object for nullness. Errors
	// result in failure being propagated upstream to Dovecot.
	Lookup(context.Context, string) (interface{}, bool, error)
}

// DictProxyServer exposes a Database using the Dovecot dict proxy
// protocol (see https://wiki2.dovecot.org/AuthDatabase/Dict).
//
// It implements the unix.LineHandler interface from the
// ai3/go-common/unix package.
type DictProxyServer struct {
	db DictDatabase
}

// NewDictProxyServer creates a new DictProxyServer.
func NewDictProxyServer(db DictDatabase) *DictProxyServer {
	return &DictProxyServer{db: db}
}

// ServeLine handles a single command.
func (p *DictProxyServer) ServeLine(ctx context.Context, lw unix.LineResponseWriter, line []byte) error {
	if len(line) < 1 {
		return errors.New("line too short")
	}

	switch line[0] {
	case 'H':
		return p.handleHello(ctx, lw, line[1:])
	case 'L':
		return p.handleLookup(ctx, lw, line[1:])
	default:
		return lw.WriteLine(failResponse)
	}
}

func (p *DictProxyServer) handleHello(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error {
	// TODO: parse the hello line and extract useful information.
	return nil
}

func (p *DictProxyServer) handleLookup(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error {
	obj, ok, err := p.db.Lookup(ctx, string(arg))
	if err != nil {
		log.Printf("error: %v", err)
		return lw.WriteLine(failResponse)
	}
	if !ok {
		return lw.WriteLine(noMatchResponse)
	}

	var buf bytes.Buffer
	buf.Write([]byte{'O'})
	if err := json.NewEncoder(&buf).Encode(obj); err != nil {
		return err
	}
	// json Encode will already add a newline.
	//buf.Write([]byte{'\n'})
	return lw.WriteLine(buf.Bytes())
}