package msdns

import (
	"encoding/json"
	"fmt"
	"runtime"

	"github.com/StackExchange/dnscontrol/v3/models"
	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
	"github.com/StackExchange/dnscontrol/v3/providers"
)

// This is the struct that matches either (or both) of the Registrar and/or DNSProvider interfaces:
type msdnsProvider struct {
	dnsserver  string      // Which DNS Server to update
	pssession  string      // Remote machine to PSSession to
	psusername string      // Remote username for PSSession
	pspassword string      // Remote password for PSSession
	shell      DNSAccessor // Handle for
}

var features = providers.DocumentationNotes{
	providers.CanGetZones:            providers.Can(),
	providers.CanUseAlias:            providers.Cannot(),
	providers.CanUseCAA:              providers.Cannot(),
	providers.CanUseDS:               providers.Unimplemented(),
	providers.CanUseNAPTR:            providers.Can(),
	providers.CanUsePTR:              providers.Can(),
	providers.CanUseSRV:              providers.Can(),
	providers.CanUseTLSA:             providers.Unimplemented(),
	providers.DocCreateDomains:       providers.Cannot("This provider assumes the zone already existing on the dns server"),
	providers.DocDualHost:            providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
	providers.DocOfficiallySupported: providers.Can(),
}

// Register with the dnscontrol system.
//   This establishes the name (all caps), and the function to call to initialize it.
func init() {
	fns := providers.DspFuncs{
		Initializer:   newDNS,
		RecordAuditor: AuditRecords,
	}
	providers.RegisterDomainServiceProviderType("MSDNS", fns, features)
}

func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {

	if runtime.GOOS != "windows" {
		fmt.Println("INFO: PowerShell not available. Disabling Active Directory provider.")
		return providers.None{}, nil
	}

	var err error

	p := &msdnsProvider{
		dnsserver:  config["dnsserver"],
		pssession:  config["pssession"],
		psusername: config["psusername"],
		pspassword: config["pspassword"],
	}
	p.shell, err = newPowerShell(config)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// Section 3: Domain Service Provider (DSP) related functions

// NB(tal): To future-proof your code, all new providers should
// implement GetDomainCorrections exactly as you see here
// (byte-for-byte the same). In 3.0
// we plan on using just the individual calls to GetZoneRecords,
// PostProcessRecords, and so on.
//
// Currently every provider does things differently, which prevents
// us from doing things like using GetZoneRecords() of a provider
// to make convertzone work with all providers.

// GetDomainCorrections get the current and existing records,
// post-process them, and generate corrections.
func (client *msdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	existing, err := client.GetZoneRecords(dc.Name)
	if err != nil {
		return nil, err
	}
	models.PostProcessRecords(existing)
	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records

	clean := PrepFoundRecords(existing)
	PrepDesiredRecords(dc)
	return client.GenerateDomainCorrections(dc, clean)
}

// GetZoneRecords gathers the DNS records and converts them to
// dnscontrol's format.
func (client *msdnsProvider) GetZoneRecords(domain string) (models.Records, error) {

	// Get the existing DNS records in native format.
	nativeExistingRecords, err := client.shell.GetDNSZoneRecords(client.dnsserver, domain)
	if err != nil {
		return nil, err
	}
	// Convert them to DNScontrol's native format:
	existingRecords := make([]*models.RecordConfig, 0, len(nativeExistingRecords))
	for _, rr := range nativeExistingRecords {
		rc, err := nativeToRecords(rr, domain)
		if err != nil {
			return nil, err
		}
		if rc != nil {
			existingRecords = append(existingRecords, rc)
		}
	}

	return existingRecords, nil
}

// PrepFoundRecords munges any records to make them compatible with
// this provider. Usually this is a no-op.
func PrepFoundRecords(recs models.Records) models.Records {
	// If there are records that need to be modified, removed, etc. we
	// do it here.  Usually this is a no-op.
	return recs
}

// PrepDesiredRecords munges any records to best suit this provider.
func PrepDesiredRecords(dc *models.DomainConfig) {
	// Sort through the dc.Records, eliminate any that can't be
	// supported; modify any that need adjustments to work with the
	// provider.  We try to do minimal changes otherwise it gets
	// confusing.

	dc.Punycode()
}

// NB(tlim): If we want to implement a registrar, refer to
// http://go.microsoft.com/fwlink/?LinkId=288158
// (Get-DnsServerZoneDelegation) for hints about which PowerShell
// commands to use.
