package commands

import (
	"bytes"
	"encoding/json"
	"fmt"
	"maps"
	"strings"

	"github.com/DNSControl/dnscontrol/v5/models"
)

// InitCredsEntry represents a single provider entry ready to be written
// into creds.json.
type InitCredsEntry struct {
	// Name is the top level key inside creds.json (for example
	// "cloudflare").
	Name string
	// TypeName is the provider TYPE (for example "CLOUDFLAREAPI").
	TypeName string
	// Fields holds all non TYPE values for this entry.
	Fields map[string]string
}

// renderCredsJSON serialises the provided entries to creds.json bytes,
// merging them on top of the provided existing map. Output uses Go's
// encoding/json so the format matches the rest of the project.
func renderCredsJSON(existing map[string]map[string]string, entries []InitCredsEntry) ([]byte, error) {
	merged := map[string]map[string]string{}
	for outerKey, innerMap := range existing {
		copyOfInner := map[string]string{}
		maps.Copy(copyOfInner, innerMap)
		merged[outerKey] = copyOfInner
	}
	for _, entry := range entries {
		inner := map[string]string{"TYPE": entry.TypeName}
		maps.Copy(inner, entry.Fields)
		merged[entry.Name] = inner
	}
	output, err := json.MarshalIndent(merged, "", "  ")
	if err != nil {
		return nil, err
	}
	return append(output, '\n'), nil
}

// InitDnsconfigChoice describes the providers chosen during `dnscontrol
// init` for the starter dnsconfig.js output.
type InitDnsconfigChoice struct {
	// RegistrarVar is the JS variable name for the registrar (for example
	// "REG_CLOUDFLARE"). Empty means no registrar was configured.
	RegistrarVar string
	// RegistrarName is the creds.json entry key for the registrar.
	RegistrarName string
	// DNSVar is the JS variable name for the DNS provider (for example
	// "DNS_CLOUDFLARE"). Empty means no DNS provider was configured.
	DNSVar string
	// DNSName is the creds.json entry key for the DNS provider.
	DNSName string
	// Domains lists the domain names to include in the starter.
	Domains []string
	// DomainRecords maps domain names to their imported DNS records.
	// Domains with an entry here get real records instead of the
	// placeholder A("@", "1.2.3.4").
	DomainRecords map[string]DomainImport
}

// DomainImport holds the imported record lines and optional DefaultTTL
// for a single domain.
type DomainImport struct {
	DefaultTTL uint32
	Records    []string
}

// renderDnsconfigJS produces a minimal dnsconfig.js body for the provided
// choice. It matches the style of
// documentation/assets/getting-started/dnsconfig.js.
func renderDnsconfigJS(choice InitDnsconfigChoice) []byte {
	var buf bytes.Buffer
	buf.WriteString("/*\n")
	buf.WriteString("   dnsconfig.js: dnscontrol configuration generated by `dnscontrol init`.\n")
	buf.WriteString("*/\n\n")
	buf.WriteString("// Providers:\n\n")

	regVar := choice.RegistrarVar
	if regVar == "" {
		regVar = "REG_NONE"
	}
	regName := choice.RegistrarName
	if regName == "" {
		regName = "none"
	}
	fmt.Fprintf(&buf, "var %s = NewRegistrar(%q);\n", regVar, regName)

	dnsVar := choice.DNSVar
	dnsName := choice.DNSName
	if dnsVar != "" && dnsName != "" {
		fmt.Fprintf(&buf, "var %s = NewDnsProvider(%q);\n", dnsVar, dnsName)
	}

	buf.WriteString("\n// Domains:\n\n")
	domains := choice.Domains
	if len(domains) == 0 {
		domains = []string{"example.com"}
	}
	for _, domain := range domains {
		if dnsVar != "" {
			fmt.Fprintf(&buf, "D(%q, %s, DnsProvider(%s),\n", domain, regVar, dnsVar)
		} else {
			fmt.Fprintf(&buf, "D(%q, %s,\n", domain, regVar)
		}
		if imp, ok := choice.DomainRecords[domain]; ok && len(imp.Records) > 0 {
			if imp.DefaultTTL != 0 && imp.DefaultTTL != models.DefaultTTL {
				fmt.Fprintf(&buf, "    DefaultTTL(%d),\n", imp.DefaultTTL)
			}
			for _, line := range imp.Records {
				fmt.Fprintf(&buf, "    %s,\n", line)
			}
		} else {
			buf.WriteString("    A(\"@\", \"1.2.3.4\")\n")
		}
		buf.WriteString(");\n\n")
	}
	return buf.Bytes()
}

// jsVarName builds a safe JavaScript variable name from a provider type.
// "CLOUDFLAREAPI" -> "DNS_CLOUDFLAREAPI", "HETZNER_V2" -> "REG_HETZNER_V2".
func jsVarName(prefix, providerType string) string {
	clean := strings.Map(func(char rune) rune {
		switch {
		case char >= 'A' && char <= 'Z':
			return char
		case char >= 'a' && char <= 'z':
			return char
		case char >= '0' && char <= '9':
			return char
		case char == '_':
			return char
		default:
			return '_'
		}
	}, strings.ToUpper(providerType))
	return prefix + "_" + clean
}

// defaultEntryName returns a sensible default creds.json entry key for the
// given provider type. "CLOUDFLAREAPI" -> "cloudflareapi_primary".
func defaultEntryName(providerType string) string {
	return strings.ToLower(providerType) + "_primary"
}
