// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail 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.
//
// ProtonMail 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 ProtonMail Bridge.  If not, see <https://www.gnu.org/licenses/>.

package main

import (
	"runtime/pprof"

	"github.com/ProtonMail/proton-bridge/internal/cmd"
	"github.com/ProtonMail/proton-bridge/internal/cookies"
	"github.com/ProtonMail/proton-bridge/internal/events"
	"github.com/ProtonMail/proton-bridge/internal/frontend"
	"github.com/ProtonMail/proton-bridge/internal/importexport"
	"github.com/ProtonMail/proton-bridge/internal/preferences"
	"github.com/ProtonMail/proton-bridge/internal/updates"
	"github.com/ProtonMail/proton-bridge/internal/users/credentials"
	"github.com/ProtonMail/proton-bridge/pkg/config"
	"github.com/ProtonMail/proton-bridge/pkg/constants"
	"github.com/ProtonMail/proton-bridge/pkg/listener"
	"github.com/ProtonMail/proton-bridge/pkg/pmapi"
	"github.com/allan-simon/go-singleinstance"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

const (
	// cacheVersion is used for cache files such as lock, or preferences.
	// Different number will drop old files and create new ones.
	cacheVersion = "c11"

	appName     = "importExport"
	appNameDash = "import-export-app"
)

var (
	log = logrus.WithField("pkg", "main") //nolint[gochecknoglobals]
)

func main() {
	cmd.Main(
		"ProtonMail Import-Export",
		"ProtonMail Import-Export app",
		nil,
		run,
	)
}

// run initializes and starts everything in a precise order.
//
// IMPORTANT: ***Read the comments before CHANGING the order ***
func run(context *cli.Context) (contextError error) { // nolint[funlen]
	// We need to have config instance to setup a logs, panic handler, etc ...
	cfg := config.New(appName, constants.Version, constants.Revision, cacheVersion)

	// We want to know about any problem. Our PanicHandler calls sentry which is
	// not dependent on anything else. If that fails, it tries to create crash
	// report which will not be possible if no folder can be created. That's the
	// only problem we will not be notified about in any way.
	panicHandler := &cmd.PanicHandler{
		AppName: "ProtonMail Import-Export app",
		Config:  cfg,
		Err:     &contextError,
	}
	defer panicHandler.HandlePanic()

	// First we need config and create necessary folder; it's dependency for everything.
	if err := cfg.CreateDirs(); err != nil {
		log.Fatal("Cannot create necessary folders: ", err)
	}

	// Setup of logs should be as soon as possible to ensure we record every wanted report in the log.
	logLevel := context.GlobalString("log-level")
	_, _ = config.SetupLog(cfg, logLevel)

	// Doesn't make sense to continue when Import-Export was invoked with wrong arguments.
	// We should tell that to the user before we do anything else.
	if context.Args().First() != "" {
		_ = cli.ShowAppHelp(context)
		return cli.NewExitError("Unknown argument", 4)
	}

	// It's safe to get version JSON file even when other instance is running.
	// (thus we put it before check of presence of other Import-Export instance).
	updates := updates.NewImportExport(cfg.GetUpdateDir())

	if dir := context.GlobalString("version-json"); dir != "" {
		cmd.GenerateVersionFiles(updates, dir)
		return nil
	}

	// Now we can try to proceed with starting the Import-Export. First we need to ensure
	// this is the only instance. If not, we will end and focus the existing one.
	lock, err := singleinstance.CreateLockFile(cfg.GetLockPath())
	if err != nil {
		log.Warn("Import-Export app is already running")
		return cli.NewExitError("Import-Export app is already running.", 3)
	}
	defer lock.Close() //nolint[errcheck]

	// In case user wants to do CPU or memory profiles...
	if doCPUProfile := context.GlobalBool("cpu-prof"); doCPUProfile {
		cmd.StartCPUProfile()
		defer pprof.StopCPUProfile()
	}

	if doMemoryProfile := context.GlobalBool("mem-prof"); doMemoryProfile {
		defer cmd.MakeMemoryProfile()
	}

	// Now we initialize all Import-Export parts.
	log.Debug("Initializing import-export...")
	eventListener := listener.New()
	events.SetupEvents(eventListener)

	credentialsStore, credentialsError := credentials.NewStore(appNameDash)
	if credentialsError != nil {
		log.Error("Could not get credentials store: ", credentialsError)
	}

	cm := pmapi.NewClientManager(cfg.GetAPIConfig())

	// Different build types have different roundtrippers (e.g. we want to enable
	// TLS fingerprint checks in production builds). GetRoundTripper has a different
	// implementation depending on whether build flag pmapi_prod is used or not.
	cm.SetRoundTripper(cfg.GetRoundTripper(cm, eventListener))

	pref := preferences.New(cfg)

	// Cookies must be persisted across restarts.
	jar, err := cookies.NewCookieJar(pref)
	if err != nil {
		logrus.WithError(err).Warn("Could not create cookie jar")
	} else {
		cm.SetCookieJar(jar)
	}

	importexportInstance := importexport.New(cfg, panicHandler, eventListener, cm, credentialsStore)

	// Decide about frontend mode before initializing rest of import-export.
	var frontendMode string
	switch {
	case context.GlobalBool("cli"):
		frontendMode = "cli"
	default:
		frontendMode = "qt"
	}
	log.WithField("mode", frontendMode).Debug("Determined frontend mode to use")

	frontend := frontend.NewImportExport(constants.Version, constants.BuildVersion, frontendMode, panicHandler, cfg, eventListener, updates, importexportInstance)

	// Last part is to start everything.
	log.Debug("Starting frontend...")
	if err := frontend.Loop(credentialsError); err != nil {
		log.Error("Frontend failed with error: ", err)
		return cli.NewExitError("Frontend error", 2)
	}

	if frontend.IsAppRestarting() {
		cmd.RestartApp()
	}

	return nil
}
