// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package cmd

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"code.gitea.io/gitea/modules/setting"

	"github.com/urfave/cli"
)

// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli.Command{
	Name:        "doctor",
	Usage:       "Diagnose the problems",
	Description: "A command to diagnose the problems of current gitea instance according the given configuration.",
	Action:      runDoctor,
}

type check struct {
	title string
	f     func(ctx *cli.Context) ([]string, error)
}

// checklist represents list for all checks
var checklist = []check{
	{
		title: "Check if OpenSSH authorized_keys file id correct",
		f:     runDoctorLocationMoved,
	},
	// more checks please append here
}

func runDoctor(ctx *cli.Context) error {
	err := initDB()
	fmt.Println("Using app.ini at", setting.CustomConf)
	if err != nil {
		fmt.Println(err)
		fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
		return nil
	}

	for i, check := range checklist {
		fmt.Println("[", i+1, "]", check.title)
		if messages, err := check.f(ctx); err != nil {
			fmt.Println("Error:", err)
		} else if len(messages) > 0 {
			for _, message := range messages {
				fmt.Println("-", message)
			}
		} else {
			fmt.Println("OK.")
		}
		fmt.Println()
	}
	return nil
}

func exePath() (string, error) {
	file, err := exec.LookPath(os.Args[0])
	if err != nil {
		return "", err
	}
	return filepath.Abs(file)
}

func runDoctorLocationMoved(ctx *cli.Context) ([]string, error) {
	if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
		return nil, nil
	}

	fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
	f, err := os.Open(fPath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var firstline string
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		firstline = strings.TrimSpace(scanner.Text())
		if len(firstline) == 0 || firstline[0] == '#' {
			continue
		}
		break
	}

	// command="/Volumes/data/Projects/gitea/gitea/gitea --config
	if len(firstline) > 0 {
		exp := regexp.MustCompile(`^[ \t]*(?:command=")([^ ]+) --config='([^']+)' serv key-([^"]+)",(?:[^ ]+) ssh-rsa ([^ ]+) ([^ ]+)[ \t]*$`)

		// command="/home/user/gitea --config='/home/user/etc/app.ini' serv key-999",option-1,option-2,option-n ssh-rsa public-key-value key-name
		res := exp.FindStringSubmatch(firstline)
		if res == nil {
			return nil, errors.New("Unknow authorized_keys format")
		}

		giteaPath := res[1] // => /home/user/gitea
		iniPath := res[2]   // => /home/user/etc/app.ini

		p, err := exePath()
		if err != nil {
			return nil, err
		}
		p, err = filepath.Abs(p)
		if err != nil {
			return nil, err
		}

		if len(giteaPath) > 0 && giteaPath != p {
			return []string{fmt.Sprintf("Gitea exe path wants %s but %s on %s", p, giteaPath, fPath)}, nil
		}
		if len(iniPath) > 0 && iniPath != setting.CustomConf {
			return []string{fmt.Sprintf("Gitea config path wants %s but %s on %s", setting.CustomConf, iniPath, fPath)}, nil
		}
	}

	return nil, nil
}