package fastdns

import (
	"fmt"
	"os"
	"reflect"

	configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1"
	"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
	"github.com/xenolf/lego/acmev2"
)

// DNSProvider is an implementation of the acmev2.ChallengeProvider interface.
type DNSProvider struct {
	config edgegrid.Config
}

// NewDNSProvider uses the supplied environment variables to return a DNSProvider instance:
// AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, AKAMAI_ACCESS_TOKEN
func NewDNSProvider() (*DNSProvider, error) {
	host := os.Getenv("AKAMAI_HOST")
	clientToken := os.Getenv("AKAMAI_CLIENT_TOKEN")
	clientSecret := os.Getenv("AKAMAI_CLIENT_SECRET")
	accessToken := os.Getenv("AKAMAI_ACCESS_TOKEN")

	return NewDNSProviderClient(host, clientToken, clientSecret, accessToken)
}

// NewDNSProviderClient uses the supplied parameters to return a DNSProvider instance
// configured for FastDNS.
func NewDNSProviderClient(host, clientToken, clientSecret, accessToken string) (*DNSProvider, error) {
	if clientToken == "" || clientSecret == "" || accessToken == "" || host == "" {
		return nil, fmt.Errorf("Akamai FastDNS credentials missing")
	}
	config := edgegrid.Config{
		Host:         host,
		ClientToken:  clientToken,
		ClientSecret: clientSecret,
		AccessToken:  accessToken,
		MaxBody:      131072,
	}

	return &DNSProvider{
		config: config,
	}, nil
}

// Present creates a TXT record to fullfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error {
	fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth)
	zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain)
	if err != nil {
		return err
	}

	configdns.Init(c.config)

	zone, err := configdns.GetZone(zoneName)
	if err != nil {
		return err
	}

	record := configdns.NewTxtRecord()
	record.SetField("name", recordName)
	record.SetField("ttl", ttl)
	record.SetField("target", value)
	record.SetField("active", true)

	existingRecord := c.findExistingRecord(zone, recordName)

	if existingRecord != nil {
		if reflect.DeepEqual(existingRecord.ToMap(), record.ToMap()) {
			return nil
		}
		zone.RemoveRecord(existingRecord)
		return c.createRecord(zone, record)
	}

	return c.createRecord(zone, record)
}

// CleanUp removes the record matching the specified parameters.
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
	fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth)
	zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain)
	if err != nil {
		return err
	}

	configdns.Init(c.config)

	zone, err := configdns.GetZone(zoneName)
	if err != nil {
		return err
	}

	existingRecord := c.findExistingRecord(zone, recordName)

	if existingRecord != nil {
		err := zone.RemoveRecord(existingRecord)
		if err != nil {
			return err
		}
		return zone.Save()
	}

	return nil
}

func (c *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) {
	zone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers)
	if err != nil {
		return "", "", err
	}
	zone = acmev2.UnFqdn(zone)
	name := acmev2.UnFqdn(fqdn)
	name = name[:len(name)-len("."+zone)]

	return zone, name, nil
}

func (c *DNSProvider) findExistingRecord(zone *configdns.Zone, recordName string) *configdns.TxtRecord {
	for _, r := range zone.Zone.Txt {
		if r.Name == recordName {
			return r
		}
	}

	return nil
}

func (c *DNSProvider) createRecord(zone *configdns.Zone, record *configdns.TxtRecord) error {
	err := zone.AddRecord(record)
	if err != nil {
		return err
	}

	return zone.Save()
}
