package api

import (
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"strings"

	"github.com/spf13/cobra"

	"github.com/gitlink-org/gitlink-cli/cmd/cmdutil"
	"github.com/gitlink-org/gitlink-cli/internal/client"
	"github.com/gitlink-org/gitlink-cli/internal/output"
)

func NewAPICmd() *cobra.Command {
	apiCmd := &cobra.Command{
		Use:   "api <METHOD> <PATH>",
		Short: "Make raw API requests to GitLink",
		Long:  `Send arbitrary HTTP requests to the GitLink API. Authentication is injected automatically.`,
		Example: `  gitlink-cli api GET /users/me
  gitlink-cli api GET /projects --query 'page=1&limit=10'
  gitlink-cli api POST /:owner/:repo/issues --body '{"subject":"Bug","description":"..."}'
  gitlink-cli api POST /:owner/:repo/issues --body-file ./issue.json`,
		Args: cobra.ExactArgs(2),
		RunE: runAPI,
	}

	apiCmd.Flags().String("body", "", "Request body (JSON string)")
	apiCmd.Flags().String("body-file", "", "Read JSON body from a file (avoids shell quoting issues)")
	apiCmd.Flags().String("query", "", "Query parameters (key=val&key2=val2)")
	apiCmd.Flags().StringSlice("header", nil, "Additional headers (key:value)")

	return apiCmd
}

func runAPI(c *cobra.Command, args []string) error {
	method := strings.ToUpper(args[0])
	path := args[1]

	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	cli, err := client.New()
	if err != nil {
		return err
	}
	cli.Debug = cmdutil.Debug

	var body interface{}
	bodyStr, _ := c.Flags().GetString("body")
	bodyFile, _ := c.Flags().GetString("body-file")
	if bodyStr != "" && bodyFile != "" {
		return printAPIError(400, "cannot use both --body and --body-file", "请只用其中一个：--body 用于内联 JSON，--body-file 用于从文件读取")
	}
	if bodyFile != "" {
		raw, err := os.ReadFile(bodyFile)
		if err != nil {
			return printAPIError(400, fmt.Sprintf("read --body-file failed: %v", err), "检查 --body-file 路径是否正确、文件是否存在且有读权限")
		}
		bodyStr = string(raw)
	}
	if bodyStr != "" {
		if err := json.Unmarshal([]byte(bodyStr), &body); err != nil {
			return printAPIError(400, fmt.Sprintf("invalid JSON body: %v", err), "确认 body 是合法 JSON；PowerShell 调用 .exe 时会剥离内嵌双引号，推荐改用 --body-file 从文件读取")
		}
	}

	var query url.Values
	queryStr, _ := c.Flags().GetString("query")
	if queryStr != "" {
		var err error
		query, err = url.ParseQuery(queryStr)
		if err != nil {
			return printAPIError(400, fmt.Sprintf("invalid query string: %v", err), "query 应为 key=value&key2=value2 形式，注意值需要 URL 编码")
		}
	}

	env, err := cli.Do(method, path, body, query)
	if err != nil {
		if apiErr, ok := err.(*client.APIError); ok {
			errEnv := output.ErrorEnvelope(apiErr.Code, apiErr.Message, apiErr.Suggestion)
			_ = output.Print(errEnv, resolveFormat())
			return cmdutil.ErrSilent
		}
		// 网络错误 / DNS 失败 / 超时等非 API 错误，也按 envelope 输出保持一致
		return printAPIError(503, fmt.Sprintf("API 请求失败 [%s %s]: %v", method, path, err), "检查网络连接、GitLink 主机可达性、token 是否有效")
	}

	return output.Print(env, resolveFormat())
}

// printAPIError 把本地校验/IO/网络错误统一按标准 envelope 输出到 stdout，
// 并返回 cmdutil.ErrSilent 让 cmd.Execute 跳过 stderr 重复打印，仅保留非零退出码。
// 设计意图：让 `api` 命令的所有错误（包括 JSON 解析、参数冲突、读文件失败、APIError、
// 网络错误）输出格式与 shortcut 一致，便于 `--format json` + jq 自动化解析。
func printAPIError(code int, message, suggestion string) error {
	env := output.ErrorEnvelope(code, message, suggestion)
	_ = output.Print(env, resolveFormat())
	return cmdutil.ErrSilent
}

func resolveFormat() string {
	f := cmdutil.Format
	if f == "" {
		return "json"
	}
	return f
}
