// Copyright 2024 - offen.software <hioffen@posteo.de>
// SPDX-License-Identifier: MPL-2.0

package main

import (
	"errors"
	"fmt"
	"runtime/debug"

	"github.com/offen/docker-volume-backup/internal/errwrap"
)

// runScript instantiates a new script object and orchestrates a backup run.
// To ensure it runs mutually exclusive a global file lock is acquired before
// it starts running. Any panic within the script will be recovered and returned
// as an error.
func runScript(c *Config) (err error) {
	defer func() {
		if derr := recover(); derr != nil {
			fmt.Printf("%s: %s\n", derr, debug.Stack())
			asErr, ok := derr.(error)
			if ok {
				err = errwrap.Wrap(asErr, "unexpected panic running script")
			} else {
				err = errwrap.Wrap(nil, fmt.Sprintf("%v", derr))
			}
		}
	}()

	s := newScript(c)

	unlock, lockErr := s.lock("/var/lock/dockervolumebackup.lock")
	if lockErr != nil {
		err = errwrap.Wrap(lockErr, "error acquiring file lock")
		return
	}
	defer func() {
		if derr := unlock(); derr != nil {
			err = errors.Join(err, errwrap.Wrap(derr, "error releasing file lock"))
		}
	}()

	unset, err := s.c.applyEnv()
	if err != nil {
		return errwrap.Wrap(err, "error applying env")
	}
	defer func() {
		if derr := unset(); derr != nil {
			err = errors.Join(err, errwrap.Wrap(derr, "error unsetting environment variables"))
		}
	}()

	if initErr := s.init(); initErr != nil {
		err = errwrap.Wrap(initErr, "error instantiating script")
		return
	}

	return func() (err error) {
		scriptErr := func() error {
			if err := s.withLabeledCommands(lifecyclePhaseArchive, func() (err error) {
				restartContainersAndServices, err := s.stopContainersAndServices()
				// The mechanism for restarting containers is not using hooks as it
				// should happen as soon as possible (i.e. before uploading backups or
				// similar).
				defer func() {
					if derr := restartContainersAndServices(); derr != nil {
						err = errors.Join(err, errwrap.Wrap(derr, "error restarting containers and services"))
					}
				}()
				if err != nil {
					return
				}
				err = s.createArchive()
				return
			})(); err != nil {
				return err
			}

			if err := s.withLabeledCommands(lifecyclePhaseProcess, s.encryptArchive)(); err != nil {
				return err
			}
			if err := s.withLabeledCommands(lifecyclePhaseCopy, s.copyArchive)(); err != nil {
				return err
			}
			if err := s.withLabeledCommands(lifecyclePhasePrune, s.pruneBackups)(); err != nil {
				return err
			}
			return nil
		}()

		if hookErr := s.runHooks(scriptErr); hookErr != nil {
			if scriptErr != nil {
				return errwrap.Wrap(
					nil,
					fmt.Sprintf(
						"error %v executing the script followed by %v calling the registered hooks",
						scriptErr,
						hookErr,
					),
				)
			}
			return errwrap.Wrap(
				hookErr,
				"the script ran successfully, but an error occurred calling the registered hooks",
			)
		}
		if scriptErr != nil {
			return errwrap.Wrap(scriptErr, "error running script")
		}
		return nil
	}()
}