// Copyright (c) 2024 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.

package clientconfig

import (
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
	"github.com/ProtonMail/proton-bridge/v3/pkg/mobileconfig"
	"golang.org/x/sys/execabs"
)

const (
	bigSurPreferencesPane  = "/System/Library/PreferencePanes/Profiles.prefPane"
	venturaPreferencesPane = "x-apple.systempreferences:com.apple.preferences.configurationprofiles"
)

type AppleMail struct{}

func (c *AppleMail) Configure(
	hostname string,
	imapPort, smtpPort int,
	imapSSL, smtpSSL bool,
	username, displayName, addresses string,
	password []byte,
) error {
	mc := prepareMobileConfig(hostname, imapPort, smtpPort, imapSSL, smtpSSL, username, displayName, addresses, password)

	confPath, err := saveConfigTemporarily(mc)
	if err != nil {
		return err
	}

	if useragent.IsBigSurOrNewer() {
		prefPane := bigSurPreferencesPane

		if useragent.IsVenturaOrNewer() {
			prefPane = venturaPreferencesPane
		}

		return execabs.Command("open", prefPane, confPath).Run() //nolint:gosec // G204 open command is safe, mobileconfig is generated by us
	}

	return execabs.Command("open", confPath).Run() //nolint:gosec // G204 open command is safe, mobileconfig is generated by us
}

func prepareMobileConfig(
	hostname string,
	imapPort, smtpPort int,
	imapSSL, smtpSSL bool,
	username, displayName, addresses string,
	password []byte,
) *mobileconfig.Config {
	return &mobileconfig.Config{
		DisplayName:        escapeXMLString(username),
		EmailAddress:       escapeXMLString(addresses),
		AccountName:        escapeXMLString(displayName),
		AccountDescription: escapeXMLString(username),
		Identifier:         escapeXMLString("protonmail " + username + strconv.FormatInt(time.Now().Unix(), 10)),
		IMAP: &mobileconfig.IMAP{
			Hostname: escapeXMLString(hostname),
			Port:     imapPort,
			TLS:      imapSSL,
			Username: escapeXMLString(username),
			Password: escapeXMLString(string(password)),
		},
		SMTP: &mobileconfig.SMTP{
			Hostname: escapeXMLString(hostname),
			Port:     smtpPort,
			TLS:      smtpSSL,
			Username: escapeXMLString(username),
			Password: escapeXMLString(string(password)),
		},
	}
}

func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
	dir, err := os.MkdirTemp("", "protonmail-autoconfig")
	if err != nil {
		return
	}

	// Make sure the temporary file is deleted.
	go func() {
		defer recover() //nolint:errcheck

		<-time.After(10 * time.Minute)
		_ = os.RemoveAll(dir)
	}()

	// Make sure the file is only readable for the current user.
	fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
	f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE, 0o600) //nolint:gosec
	if err != nil {
		return
	}

	if err = mc.WriteOut(f); err != nil {
		_ = f.Close()
		return
	}
	_ = f.Close()

	return
}

// escapeXMLString replace all occurrences of the 5 characters `&`, `<`, `>`, `"` and `'` by their respective escaped version as per the XML spec.
// https://www.w3.org/TR/xml/#syntax
func escapeXMLString(input string) string {
	result := strings.ReplaceAll(input, `&`, `&amp;`)
	result = strings.ReplaceAll(result, `<`, `&lt;`)
	result = strings.ReplaceAll(result, `>`, `&gt;`)
	result = strings.ReplaceAll(result, `"`, `&quot;`)
	return strings.ReplaceAll(result, `'`, `&apos;`)
}
