package vultr

import (
	"context"
	"fmt"
	"sync"

	"golang.org/x/oauth2"

	"github.com/libdns/libdns"
	"github.com/vultr/govultr/v3"
)

type Client struct {
	vultr *govultr.Client
	mutex sync.Mutex
}

func (p *Provider) getClient() error {
	if p.client.vultr == nil {
		oauth_cfg := &oauth2.Config{}
		oauth_token_source := oauth_cfg.TokenSource(context.TODO(), &oauth2.Token{AccessToken: p.APIToken})

		p.client.vultr = govultr.NewClient(oauth2.NewClient(context.TODO(), oauth_token_source))
	}

	return nil
}

func (p *Provider) getDNSEntries(ctx context.Context, domain string) ([]libdns.Record, error) {
	p.getClient()

	listOptions := &govultr.ListOptions{}

	var records []libdns.Record
	for {
		dns_entries, meta, _, err := p.client.vultr.DomainRecord.List(ctx, domain, listOptions)
		if err != nil {
			return records, err
		}

		for _, entry := range dns_entries {
			record := fromAPIRecord(entry, domain)
			records = append(records, record)
		}

		if meta.Links.Next == "" {
			break
		}

		listOptions.Cursor = meta.Links.Next
	}

	return records, nil
}

func (p *Provider) addDNSRecord(ctx context.Context, domain string, r libdns.Record) (libdns.Record, error) {
	p.client.mutex.Lock()
	defer p.client.mutex.Unlock()

	p.getClient()

	domainRecordReq := toDomainRecordReq(r)

	rec, _, err := p.client.vultr.DomainRecord.Create(ctx, domain, &domainRecordReq)
	if err != nil {
		return r, err
	}

	record := fromLibdnsRecord(r, rec.ID)

	return record, nil
}

func (p *Provider) removeDNSRecord(ctx context.Context, domain string, record libdns.Record) (libdns.Record, error) {
	p.client.mutex.Lock()
	defer p.client.mutex.Unlock()

	p.getClient()

	recordId, err := getRecordId(record)
	if err != nil {
		// try to get the ID from API if we don't have it
		records, err := p.getDNSEntries(ctx, domain)
		if err != nil {
			return record, fmt.Errorf("could not get record ID from API")
		}

		for _, rec := range records {
			if rec.RR().Name == record.RR().Name {
				recordId = rec.(VultrRecord).ID
			}
		}
	}

	err = p.client.vultr.DomainRecord.Delete(ctx, domain, recordId)
	if err != nil {
		return record, err
	}

	return record, nil
}

func (p *Provider) updateDNSRecord(ctx context.Context, domain string, record libdns.Record) (libdns.Record, error) {
	p.client.mutex.Lock()
	defer p.client.mutex.Unlock()

	p.getClient()

	recordId, err := getRecordId(record)
	if err != nil {
		// try to get the ID from API if we don't have it
		records, err := p.getDNSEntries(ctx, domain)
		if err != nil {
			return record, fmt.Errorf("could not get record ID from API")
		}

		for _, rec := range records {
			if rec.RR().Data == record.RR().Data {
				recordId = rec.(VultrRecord).ID
			}
		}
	}

	domainRecordReq := toDomainRecordReq(record)

	err = p.client.vultr.DomainRecord.Update(ctx, domain, recordId, &domainRecordReq)
	if err != nil {
		return record, err
	}

	return record, nil
}

func (p *Provider) getDNSZones(ctx context.Context) ([]libdns.Zone, error) {
	p.client.mutex.Lock()
	defer p.client.mutex.Unlock()

	p.getClient()

	listOptions := &govultr.ListOptions{}

	var zones []libdns.Zone
	for {
		dns_zones, meta, _, err := p.client.vultr.Domain.List(ctx, listOptions)
		if err != nil {
			return zones, err
		}

		for _, entry := range dns_zones {
			zone := libdns.Zone{
				Name: entry.Domain,
			}
			zones = append(zones, zone)
		}

		if meta.Links.Next == "" {
			break
		}

		listOptions.Cursor = meta.Links.Next
	}

	return zones, nil
}
