// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.

// TODO: Add logging
package main

import (
	"bufio"
	"fmt"
	"log"
	"log/slog"
	"os"
	"os/exec"
	"os/user"
	"runtime"
	"strconv"
	"strings"
)

func findConfig() (string, error) {
	// Get the current user.
	curUser, err := user.Current()
	if err != nil {
		return "", fmt.Errorf("find config: failed to get current user: %w", err)
	}
	// Get home directory.
	home := curUser.HomeDir
	if home == "" {
		return "", fmt.Errorf("find config: no home directory found")
	}
	osConfigDir := os.Getenv("XDG_CONFIG_HOME")
	if osConfigDir == "" {
		osConfigDir = home + "/.config"
	}
	configFiles := [3]string{
		osConfigDir + "/go-sendxmpp/config",
		osConfigDir + "/go-sendxmpp/sendxmpprc",
		home + "/.sendxmpprc",
	}

	for _, r := range configFiles {
		// Check that the config file is existing.
		_, err := os.Stat(r)
		if err == nil {
			return r, nil
		}
	}
	return "", fmt.Errorf("find config: no configuration file found")
}

// Opens the config file and returns the specified values
// for username, server and port.
func parseConfig(configPath string) (output configuration, err error) {
	// Use $XDG_CONFIG_HOME/.config/go-sendxmpp/config,
	// $XDG_CONFIG_HOME/.config/go-sendxmpp/sendxmpprc or
	// ~/.sendxmpprc if no config path is specified.
	// Get systems user config path.
	if configPath == "" {
		configPath, err = findConfig()
		if err != nil {
			log.Fatal(err)
		}
	}

	// Only check file permissions if we are not running on windows.
	if runtime.GOOS != "windows" {
		info, err := os.Stat(configPath)
		if err != nil {
			log.Fatal(err)
		}
		// Check for file permissions. Must be 600, 640, 440 or 400.
		perm := info.Mode().Perm()
		permissions := strconv.FormatInt(int64(perm), 8)
		if permissions != "600" && permissions != "640" && permissions != "440" && permissions != "400" {
			return output, fmt.Errorf("parse config: wrong permissions for %s: %s instead of 400 (recommended), 440, 600 or 640", configPath, permissions)
		}
	}

	// Open config file.
	// #nosec G304 -- config path comes from the local system, it's no untrusted input from the network
	file, err := os.Open(configPath)
	if err != nil {
		return output, fmt.Errorf("parse config: failed to open config file: %w", err)
	}
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)

	// Read config file per line.
	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), "#") {
			continue
		}

		column := strings.SplitN(scanner.Text(), " ", defaultConfigColumnSep)

		if len(column) > 1 {
			switch column[0] {
			case "username:":
				output.username = column[1]
			case "jserver:":
				output.jserver = column[1]
			case "password:":
				output.password = column[1]
			case "eval_password:":
				shell := os.Getenv("SHELL")
				if shell == "" {
					shell = "/bin/sh"
				}
				// #nosec G204,G702 -- command to unlock password manager is supplied by the user
				// in the config and no untrusted data
				out, err := exec.Command(shell, "-c", column[1]).Output()
				if err != nil {
					err2 := file.Close()
					if err2 != nil {
						slog.Warn("closing file failed", "error", err)
					}
					log.Fatal(err)
				}
				output.password = string(out)
				if output.password[len(output.password)-1] == '\n' {
					output.password = output.password[:len(output.password)-1]
				}
			case "port:":
				output.port = column[1]
			case "alias:":
				output.alias = column[1]
			case "allow_plain:":
				if strings.ToLower(column[1]) == "true" {
					output.allowPLAIN = true
				} else {
					output.allowPLAIN = false
				}
			default:
				// Try to parse legacy sendxmpp config files.
				if len(column) >= defaultConfigColumnSep && strings.Contains(column[0], "@") {
					if strings.Contains(scanner.Text(), ";") {
						output.username = strings.Split(column[0], ";")[0]
						output.jserver = strings.Split(column[0], ";")[1]
						output.password = column[1]
					} else {
						output.username = strings.Split(column[0], ":")[0]
						if strings.Contains(output.username, "@") {
							jserver := strings.SplitAfter(output.username, "@")[1]
							if len(jserver) < defaultLenServerConf {
								log.Fatal("Couldn't parse config: ", column[0])
							}
							output.jserver = jserver
						}
						output.password = column[1]
					}
				}
			}
		}
	}

	// Check if the username is a valid JID
	output.username, err = MarshalJID(output.username)
	if err != nil {
		// Check whether only the local part was used by appending an @ and the
		// server part.
		output.username = output.username + "@" + output.jserver
		// Check if the username is a valid JID now
		output.username, err = MarshalJID(output.username)
		if err != nil {
			return output, fmt.Errorf("parse config: invalid username/JID: %s", output.username)
		}
	}

	err2 := file.Close()
	if err2 != nil {
		slog.Warn("closing file failed", "error", err)
	}
	return output, err
}
