package tsig

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/coredns/caddy"
	"github.com/coredns/coredns/core/dnsserver"
	"github.com/coredns/coredns/plugin"

	"github.com/miekg/dns"
)

func init() {
	caddy.RegisterPlugin(pluginName, caddy.Plugin{
		ServerType: "dns",
		Action:     setup,
	})
}

func setup(c *caddy.Controller) error {
	t, err := parse(c)
	if err != nil {
		return plugin.Error(pluginName, c.ArgErr())
	}

	config := dnsserver.GetConfig(c)

	config.TsigSecret = t.secrets

	config.AddPlugin(func(next plugin.Handler) plugin.Handler {
		t.Next = next
		return t
	})

	return nil
}

func parse(c *caddy.Controller) (*TSIGServer, error) {
	t := &TSIGServer{
		secrets: make(map[string]string),
		types:   defaultQTypes,
		opcodes: defaultOpCodes,
	}

	for i := 0; c.Next(); i++ {
		if i > 0 {
			return nil, plugin.ErrOnce
		}

		t.Zones = plugin.OriginsFromArgsOrServerBlock(c.RemainingArgs(), c.ServerBlockKeys)
		for c.NextBlock() {
			switch c.Val() {
			case "secret":
				args := c.RemainingArgs()
				if len(args) != 2 {
					return nil, c.ArgErr()
				}
				k := plugin.Name(args[0]).Normalize()
				if _, exists := t.secrets[k]; exists {
					return nil, fmt.Errorf("key %q redefined", k)
				}
				t.secrets[k] = args[1]
			case "secrets":
				args := c.RemainingArgs()
				if len(args) != 1 {
					return nil, c.ArgErr()
				}
				f, err := os.Open(args[0])
				if err != nil {
					return nil, err
				}
				secrets, err := parseKeyFile(f)
				if err != nil {
					return nil, err
				}
				for k, s := range secrets {
					if _, exists := t.secrets[k]; exists {
						return nil, fmt.Errorf("key %q redefined", k)
					}
					t.secrets[k] = s
				}
			case "require":
				t.types = qTypes{}
				args := c.RemainingArgs()
				if len(args) == 0 {
					return nil, c.ArgErr()
				}
				if args[0] == "all" {
					t.allTypes = true
					continue
				}
				if args[0] == "none" {
					continue
				}
				for _, str := range args {
					qt, ok := dns.StringToType[str]
					if !ok {
						return nil, c.Errf("unknown query type '%s'", str)
					}
					t.types[qt] = struct{}{}
				}
			case "require_opcode":
				t.opcodes = opCodes{}
				args := c.RemainingArgs()
				if len(args) == 0 {
					return nil, c.ArgErr()
				}
				if args[0] == "all" {
					t.allOpcodes = true
					continue
				}
				if args[0] == "none" {
					continue
				}
				for _, str := range args {
					op, ok := dns.StringToOpcode[str]
					if !ok {
						return nil, c.Errf("unknown opcode '%s'", str)
					}
					t.opcodes[op] = struct{}{}
				}
			default:
				return nil, c.Errf("unknown property '%s'", c.Val())
			}
		}
	}
	return t, nil
}

func parseKeyFile(f io.Reader) (map[string]string, error) {
	secrets := make(map[string]string)
	s := bufio.NewScanner(f)
	for s.Scan() {
		fields := strings.Fields(s.Text())
		if len(fields) == 0 {
			continue
		}
		if fields[0] != "key" {
			return nil, fmt.Errorf("unexpected token %q", fields[0])
		}
		if len(fields) < 2 {
			return nil, fmt.Errorf("expected key name %q", s.Text())
		}
		key := strings.Trim(fields[1], "\"{")
		if len(key) == 0 {
			return nil, fmt.Errorf("expected key name %q", s.Text())
		}
		key = plugin.Name(key).Normalize()
		if _, ok := secrets[key]; ok {
			return nil, fmt.Errorf("key %q redefined", key)
		}
	key:
		for s.Scan() {
			fields := strings.Fields(s.Text())
			if len(fields) == 0 {
				continue
			}
			switch fields[0] {
			case "algorithm":
				continue
			case "secret":
				if len(fields) < 2 {
					return nil, fmt.Errorf("expected secret key %q", s.Text())
				}
				secret := strings.Trim(fields[1], "\";")
				if len(secret) == 0 {
					return nil, fmt.Errorf("expected secret key %q", s.Text())
				}
				secrets[key] = secret
			case "}":
				fallthrough
			case "};":
				break key
			default:
				return nil, fmt.Errorf("unexpected token %q", fields[0])
			}
		}
		if _, ok := secrets[key]; !ok {
			return nil, fmt.Errorf("expected secret for key %q", key)
		}
	}
	return secrets, nil
}

var defaultQTypes = qTypes{}
var defaultOpCodes = opCodes{}
