package auroradns

import (
	"fmt"
	"github.com/edeckers/auroradnsclient"
	"github.com/edeckers/auroradnsclient/records"
	"github.com/edeckers/auroradnsclient/zones"
	"github.com/xenolf/lego/acme"
	"os"
	"sync"
)

// DNSProvider describes a provider for AuroraDNS
type DNSProvider struct {
	recordIDs   map[string]string
	recordIDsMu sync.Mutex
	client      *auroradnsclient.AuroraDNSClient
}

// NewDNSProvider returns a DNSProvider instance configured for AuroraDNS.
// Credentials must be passed in the environment variables: AURORA_USER_ID
// and AURORA_KEY.
func NewDNSProvider() (*DNSProvider, error) {
	userID := os.Getenv("AURORA_USER_ID")
	key := os.Getenv("AURORA_KEY")

	endpoint := os.Getenv("AURORA_ENDPOINT")
	if endpoint == "" {
		endpoint = "https://api.auroradns.eu"
	}

	return NewDNSProviderCredentials(endpoint, userID, key)
}

// NewDNSProviderCredentials uses the supplied credentials to return a
// DNSProvider instance configured for AuroraDNS.
func NewDNSProviderCredentials(baseURL string, userID string, key string) (*DNSProvider, error) {
	client, err := auroradnsclient.NewAuroraDNSClient(baseURL, userID, key)
	if err != nil {
		return nil, err
	}

	return &DNSProvider{
		client:    client,
		recordIDs: make(map[string]string),
	}, nil
}

func (provider *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, error) {
	zs, err := provider.client.GetZones()

	if err != nil {
		return zones.ZoneRecord{}, err
	}

	for _, element := range zs {
		if element.Name == name {
			return element, nil
		}
	}

	return zones.ZoneRecord{}, fmt.Errorf("Could not find Zone record")
}

// Present creates a record with a secret
func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
	fqdn, value, _ := acme.DNS01Record(domain, keyAuth)

	authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
	if err != nil {
		return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err)
	}

	// 1. Aurora will happily create the TXT record when it is provided a fqdn,
	//    but it will only appear in the control panel and will not be
	//    propagated to DNS servers. Extract and use subdomain instead.
	// 2. A trailing dot in the fqdn will cause Aurora to add a trailing dot to
	//    the subdomain, resulting in _acme-challenge..<domain> rather
	//    than _acme-challenge.<domain>

	subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]

	authZone = acme.UnFqdn(authZone)

	zoneRecord, err := provider.getZoneInformationByName(authZone)

	reqData :=
		records.CreateRecordRequest{
			RecordType: "TXT",
			Name:       subdomain,
			Content:    value,
			TTL:        300,
		}

	respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData)
	if err != nil {
		return fmt.Errorf("Could not create record: '%s'.", err)
	}

	provider.recordIDsMu.Lock()
	provider.recordIDs[fqdn] = respData.ID
	provider.recordIDsMu.Unlock()

	return nil
}

// CleanUp removes a given record that was generated by Present
func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error {
	fqdn, _, _ := acme.DNS01Record(domain, keyAuth)

	provider.recordIDsMu.Lock()
	recordID, ok := provider.recordIDs[fqdn]
	provider.recordIDsMu.Unlock()

	if !ok {
		return fmt.Errorf("Unknown recordID for '%s'", fqdn)
	}

	authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
	if err != nil {
		return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err)
	}

	authZone = acme.UnFqdn(authZone)

	zoneRecord, err := provider.getZoneInformationByName(authZone)
	if err != nil {
		return err
	}

	_, err = provider.client.RemoveRecord(zoneRecord.ID, recordID)
	if err != nil {
		return err
	}

	provider.recordIDsMu.Lock()
	delete(provider.recordIDs, fqdn)
	provider.recordIDsMu.Unlock()

	return nil
}
