// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package commands

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"

	"code.vikunja.io/veans/internal/bootstrap"
	"code.vikunja.io/veans/internal/config"
)

type initFlags struct {
	server          string
	token           string
	username        string
	password        string
	totp            string
	usePassword     bool
	botUsername     string
	projectID       int64
	viewID          int64
	yesBuckets      bool
	skipBuckets     bool
	configPath      string
	installClaude   bool
	installOpenCode bool
	noHooks         bool
}

func newInitCmd() *cobra.Command {
	f := &initFlags{}
	cmd := &cobra.Command{
		Use:   "init",
		Short: "Provision a Vikunja bot user and write .veans.yml",
		Long: `Onboards veans into the current repository:

  1. Authenticate as you (--token, or username/password)
  2. Pick a Vikunja project and Kanban view
  3. Bootstrap canonical buckets (Todo / In Progress / In Review / Done / Scrapped)
  4. Create a 'bot-<repo>' user, share the project with it, mint its API token
  5. Store the bot's token in your keychain (or ~/.config/veans/credentials.yml)
  6. Write .veans.yml to the repository root

The token stored locally belongs to the bot, not to you — you can rotate or
revoke it at any time without affecting your own session.`,
		RunE: func(cmd *cobra.Command, _ []string) error {
			path := f.configPath
			if path == "" {
				root, err := config.RepoRoot(cmd.Context(), "")
				if err != nil {
					return err
				}
				path = filepath.Join(root, config.Filename)
			}
			res, err := bootstrap.Init(cmd.Context(), &bootstrap.Options{
				ConfigPath:          path,
				Server:              f.server,
				HumanToken:          f.token,
				HumanUsePassword:    f.usePassword,
				HumanUsername:       f.username,
				HumanPassword:       f.password,
				HumanTOTP:           f.totp,
				BotUsername:         f.botUsername,
				ProjectID:           f.projectID,
				ViewID:              f.viewID,
				AutoApproveBuckets:  f.yesBuckets,
				SkipBucketBootstrap: f.skipBuckets,
				InstallClaudeCode:   f.installClaude,
				InstallOpenCode:     f.installOpenCode,
				ClaudeCodeFlagSet:   cmd.Flags().Changed("install-claude"),
				OpenCodeFlagSet:     cmd.Flags().Changed("install-opencode"),
				NoHooks:             f.noHooks,
				Out:                 os.Stderr,
			})
			if err != nil {
				return err
			}
			printPostInitSummary(cmd.OutOrStdout(), res)
			return nil
		},
	}

	cmd.Flags().StringVar(&f.server, "server", "", "Vikunja server URL")
	cmd.Flags().StringVar(&f.token, "token", "", "JWT or personal API token (skips OAuth/password; useful for SSO/OIDC instances)")
	cmd.Flags().BoolVar(&f.usePassword, "use-password", false, "use POST /login (username+password) instead of the default OAuth flow")
	cmd.Flags().StringVar(&f.username, "username", "", "Vikunja username (implies --use-password)")
	cmd.Flags().StringVar(&f.password, "password", "", "Vikunja password (implies --use-password; prompted if empty)")
	cmd.Flags().StringVar(&f.totp, "totp", "", "TOTP code if your account requires 2FA")
	cmd.Flags().StringVar(&f.botUsername, "bot-username", "", "override the bot-<repo> default")
	cmd.Flags().Int64Var(&f.projectID, "project", 0, "skip the interactive project picker")
	cmd.Flags().Int64Var(&f.viewID, "view", 0, "skip the interactive view picker")
	cmd.Flags().BoolVar(&f.yesBuckets, "yes-buckets", false, "auto-approve canonical bucket bootstrap")
	cmd.Flags().BoolVar(&f.skipBuckets, "skip-buckets", false, "do not prompt or create buckets (assumes they exist)")
	cmd.Flags().StringVar(&f.configPath, "config", "", "where to write .veans.yml (defaults to the repo root)")
	cmd.Flags().BoolVar(&f.installClaude, "install-claude", false, "wire `veans prime` into .claude/settings.json (skip prompt)")
	cmd.Flags().BoolVar(&f.installOpenCode, "install-opencode", false, "wire `veans prime` into .opencode/plugin/veans-prime.ts (skip prompt)")
	cmd.Flags().BoolVar(&f.noHooks, "no-hooks", false, "don't offer to install agent hooks; just print the snippets")

	return cmd
}

func printPostInitSummary(w io.Writer, res *bootstrap.Result) {
	fmt.Fprintf(w, "\nveans is ready. Bot user: %s\n", res.BotUser.Username)
	fmt.Fprintf(w, "Config:    %s\n", res.Config.Path())
	fmt.Fprintf(w, "Project:   #%d %s\n", res.Config.ProjectID, identOrFallback(res.Config.ProjectIdentifier))

	// Only fall back to printing the snippets when the user declined or
	// skipped the install offer. When at least one hook was installed, the
	// install routine already logged what it did to stderr.
	if res.AgentChoices.ClaudeCode || res.AgentChoices.OpenCode {
		return
	}
	// Snippets are sourced from the bootstrap package so manual installs
	// stay byte-for-byte equivalent to what `installAgentHooks` would have
	// written — if a new hook event is added there, it shows up here too.
	fmt.Fprintf(w, `
To wire veans into your coding agent later, paste one of these snippets:

Claude Code (%s):
%s

OpenCode (%s):
%s`, bootstrap.ClaudeCodeSettingsRelPath, indent(bootstrap.ClaudeCodeHookSnippet(), "  "),
		bootstrap.OpenCodePluginRelPath, indent(bootstrap.OpenCodePluginSnippet, "  "))
}

// indent prefixes every line of s with prefix. Used to inset the embedded
// snippets under their "Claude Code:" / "OpenCode:" headings without
// hard-coding the indent inside the bootstrap package's snippet strings.
func indent(s, prefix string) string {
	if s == "" {
		return s
	}
	lines := strings.Split(strings.TrimRight(s, "\n"), "\n")
	for i, line := range lines {
		if line == "" {
			continue
		}
		lines[i] = prefix + line
	}
	return strings.Join(lines, "\n")
}

func identOrFallback(s string) string {
	if s == "" {
		return "(no identifier — task IDs render as #NN)"
	}
	return s
}
