package home

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"path"

	"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
	"github.com/AdguardTeam/golibs/log"
	uuid "github.com/satori/go.uuid"
	"howett.net/plist"
)

type dnsSettings struct {
	DNSProtocol string
	ServerURL   string `plist:",omitempty"`
	ServerName  string `plist:",omitempty"`
	clientID    string
}

type payloadContent struct {
	Name               string
	PayloadDescription string
	PayloadDisplayName string
	PayloadIdentifier  string
	PayloadType        string
	PayloadUUID        string
	DNSSettings        dnsSettings
	PayloadVersion     int
}

type mobileConfig struct {
	PayloadDescription       string
	PayloadDisplayName       string
	PayloadIdentifier        string
	PayloadType              string
	PayloadUUID              string
	PayloadContent           []payloadContent
	PayloadVersion           int
	PayloadRemovalDisallowed bool
}

func genUUIDv4() string {
	return uuid.NewV4().String()
}

const (
	dnsProtoHTTPS = "HTTPS"
	dnsProtoTLS   = "TLS"
)

func getMobileConfig(d dnsSettings) ([]byte, error) {
	var dspName string
	switch d.DNSProtocol {
	case dnsProtoHTTPS:
		dspName = fmt.Sprintf("%s DoH", d.ServerName)

		u := &url.URL{
			Scheme: schemeHTTPS,
			Host:   d.ServerName,
			Path:   "/dns-query",
		}
		if d.clientID != "" {
			u.Path = path.Join(u.Path, d.clientID)
		}

		d.ServerURL = u.String()
	case dnsProtoTLS:
		dspName = fmt.Sprintf("%s DoT", d.ServerName)
		if d.clientID != "" {
			d.ServerName = d.clientID + "." + d.ServerName
		}
	default:
		return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol)
	}

	data := mobileConfig{
		PayloadContent: []payloadContent{{
			Name:               dspName,
			PayloadDescription: "Configures device to use AdGuard Home",
			PayloadDisplayName: dspName,
			PayloadIdentifier:  fmt.Sprintf("com.apple.dnsSettings.managed.%s", genUUIDv4()),
			PayloadType:        "com.apple.dnsSettings.managed",
			PayloadUUID:        genUUIDv4(),
			PayloadVersion:     1,
			DNSSettings:        d,
		}},
		PayloadDescription:       "Adds AdGuard Home to Big Sur and iOS 14 or newer systems",
		PayloadDisplayName:       dspName,
		PayloadIdentifier:        genUUIDv4(),
		PayloadRemovalDisallowed: false,
		PayloadType:              "Configuration",
		PayloadUUID:              genUUIDv4(),
		PayloadVersion:           1,
	}

	return plist.MarshalIndent(data, plist.XMLFormat, "\t")
}

func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
	var err error

	q := r.URL.Query()
	host := q.Get("host")
	if host == "" {
		host = Context.tls.conf.ServerName
	}

	if host == "" {
		w.WriteHeader(http.StatusInternalServerError)

		const msg = "no host in query parameters and no server_name"
		err = json.NewEncoder(w).Encode(&jsonError{
			Message: msg,
		})
		if err != nil {
			log.Debug("writing 500 json response: %s", err)
		}

		return
	}

	clientID := q.Get("client_id")
	if clientID != "" {
		err = dnsforward.ValidateClientID(clientID)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)

			err = json.NewEncoder(w).Encode(&jsonError{
				Message: err.Error(),
			})
			if err != nil {
				log.Debug("writing 400 json response: %s", err)
			}

			return
		}
	}

	d := dnsSettings{
		DNSProtocol: dnsp,
		ServerName:  host,
		clientID:    clientID,
	}

	mobileconfig, err := getMobileConfig(d)
	if err != nil {
		httpError(w, http.StatusInternalServerError, "plist.MarshalIndent: %s", err)

		return
	}

	w.Header().Set("Content-Type", "application/xml")

	const (
		dohContDisp = `attachment; filename=doh.mobileconfig`
		dotContDisp = `attachment; filename=dot.mobileconfig`
	)

	contDisp := dohContDisp
	if dnsp == dnsProtoTLS {
		contDisp = dotContDisp
	}

	w.Header().Set("Content-Disposition", contDisp)

	_, _ = w.Write(mobileconfig)
}

func handleMobileConfigDOH(w http.ResponseWriter, r *http.Request) {
	handleMobileConfig(w, r, dnsProtoHTTPS)
}

func handleMobileConfigDOT(w http.ResponseWriter, r *http.Request) {
	handleMobileConfig(w, r, dnsProtoTLS)
}
