package project

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/databricks/cli/cmd/labs/github"
	"github.com/databricks/cli/libs/log"
	"github.com/fatih/color"
	"github.com/spf13/cobra"
)

type installable interface {
	Install(ctx context.Context) error
}

type devInstallation struct {
	*Project
	*cobra.Command
}

func (d *devInstallation) Install(ctx context.Context) error {
	if d.Installer == nil {
		return nil
	}
	_, err := d.Installer.validLogin(d.Command)
	if errors.Is(err, ErrNoLoginConfig) {
		cfg, err := d.Installer.envAwareConfig(ctx)
		if err != nil {
			return err
		}
		lc := &loginConfig{Entrypoint: d.Installer.Entrypoint}
		_, err = lc.askWorkspace(ctx, cfg)
		if err != nil {
			return fmt.Errorf("ask for workspace: %w", err)
		}
		err = lc.askAccountProfile(ctx, cfg)
		if err != nil {
			return fmt.Errorf("ask for account: %w", err)
		}
		err = lc.EnsureFoldersExist()
		if err != nil {
			return fmt.Errorf("folders: %w", err)
		}
		err = lc.save(ctx)
		if err != nil {
			return fmt.Errorf("save: %w", err)
		}
	}
	return d.Installer.runHook(d.Command)
}

func NewInstaller(cmd *cobra.Command, name string) (installable, error) {
	if name == "." {
		wd, err := os.Getwd()
		if err != nil {
			return nil, fmt.Errorf("working directory: %w", err)
		}
		prj, err := Load(cmd.Context(), filepath.Join(wd, "labs.yml"))
		if err != nil {
			return nil, fmt.Errorf("load: %w", err)
		}
		cmd.PrintErrln(color.YellowString("Installing %s in development mode from %s", prj.Name, wd))
		return &devInstallation{
			Project: prj,
			Command: cmd,
		}, nil
	}
	name, version, ok := strings.Cut(name, "@")
	if !ok {
		version = "latest"
	}
	f := &fetcher{name}
	version, err := f.checkReleasedVersions(cmd, version)
	if err != nil {
		return nil, fmt.Errorf("version: %w", err)
	}
	prj, err := f.loadRemoteProjectDefinition(cmd, version)
	if err != nil {
		return nil, fmt.Errorf("remote: %w", err)
	}
	return &installer{
		Project: prj,
		version: version,
		cmd:     cmd,
	}, nil
}

func NewUpgrader(cmd *cobra.Command, name string) (*installer, error) {
	f := &fetcher{name}
	version, err := f.checkReleasedVersions(cmd, "latest")
	if err != nil {
		return nil, fmt.Errorf("version: %w", err)
	}
	prj, err := f.loadRemoteProjectDefinition(cmd, version)
	if err != nil {
		return nil, fmt.Errorf("remote: %w", err)
	}
	prj.folder, err = PathInLabs(cmd.Context(), name)
	if err != nil {
		return nil, err
	}
	return &installer{
		Project: prj,
		version: version,
		cmd:     cmd,
	}, nil
}

type fetcher struct {
	name string
}

func (f *fetcher) checkReleasedVersions(cmd *cobra.Command, version string) (string, error) {
	ctx := cmd.Context()
	cacheDir, err := PathInLabs(ctx, f.name, "cache")
	if err != nil {
		return "", err
	}
	// `databricks labs isntall X` doesn't know which exact version to fetch, so first
	// we fetch all versions and then pick the latest one dynamically.
	versions, err := github.NewReleaseCache("databrickslabs", f.name, cacheDir).Load(ctx)
	if err != nil {
		return "", fmt.Errorf("versions: %w", err)
	}
	for _, v := range versions {
		if v.Version == version {
			return version, nil
		}
	}
	if version == "latest" && len(versions) > 0 {
		log.Debugf(ctx, "Latest %s version is: %s", f.name, versions[0].Version)
		return versions[0].Version, nil
	}
	cmd.PrintErrln(color.YellowString("[WARNING] Installing unreleased version: %s", version))
	return version, nil
}

func (i *fetcher) loadRemoteProjectDefinition(cmd *cobra.Command, version string) (*Project, error) {
	ctx := cmd.Context()
	raw, err := github.ReadFileFromRef(ctx, "databrickslabs", i.name, version, "labs.yml")
	if err != nil {
		return nil, fmt.Errorf("read labs.yml from GitHub: %w", err)
	}
	return readFromBytes(ctx, raw)
}