package edgecmd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
	"gopkg.in/yaml.v3"

	"iop/apps/edge/internal/configrefresh"
	"iop/apps/edge/internal/edgevalidate"
	"iop/packages/go/config"
	"iop/packages/go/hostsetup"
)

func LoadEdgeConfig(cmd *cobra.Command) (*config.EdgeConfig, string, error) {
	path, err := ResolveConfigPath(cmd)
	if err != nil {
		return nil, "", err
	}
	cfg, err := config.LoadEdge(path)
	if err != nil {
		return nil, "", fmt.Errorf("load config: %w", err)
	}
	return cfg, path, nil
}

func configCmd() *cobra.Command {
	c := &cobra.Command{
		Use:   "config",
		Short: "Create, inspect, and validate edge config",
		Long: `Create, inspect, and validate the bundle-local edge.yaml configuration.

Typical local flow:
  1. iop-edge config init
  2. iop-edge env
  3. iop-edge node register <node-id>
  4. iop-edge config check
  5. iop-edge serve`,
		Example: `  iop-edge config init
  iop-edge env
  iop-edge node register node-silicon-ollama --adapter ollama
  iop-edge config check`,
	}
	c.AddCommand(configInitCmd(), configPrintCmd(), configCheckCmd(), configRefreshCmd())
	return c
}

func configInitCmd() *cobra.Command {
	output := "edge.yaml"
	var force bool
	var stdout bool
	c := &cobra.Command{
		Use:   "init",
		Short: "Write an edge.yaml template",
		Long: "Write a deployable edge.yaml template to the current bundle directory or stdout.\n" +
			"Run this beside a local iop-edge binary, then run `iop-edge config check`.",
		Example: "  iop-edge config init\n" +
			"  iop-edge config init --stdout\n" +
			"  iop-edge config init --output edge.yaml\n" +
			"  iop-edge config check",
		RunE: func(cmd *cobra.Command, _ []string) error {
			template := []byte(hostsetup.EdgeBundleConfigTemplate())
			if stdout || output == "-" {
				_, err := cmd.OutOrStdout().Write(template)
				return err
			}
			if !force {
				if _, err := os.Stat(output); err == nil {
					return fmt.Errorf("%s already exists (use --force to overwrite)", output)
				} else if !errors.Is(err, os.ErrNotExist) {
					return fmt.Errorf("stat output: %w", err)
				}
			}
			if dir := filepath.Dir(output); dir != "" && dir != "." {
				if err := os.MkdirAll(dir, 0o755); err != nil {
					return fmt.Errorf("mkdir %s: %w", dir, err)
				}
			}
			if err := os.WriteFile(output, template, 0o600); err != nil {
				return fmt.Errorf("write %s: %w", output, err)
			}
			fmt.Fprintf(cmd.OutOrStdout(), "wrote %s\n", output)
			return nil
		},
	}
	c.Flags().StringVarP(&output, "output", "o", output, "output path")
	c.Flags().BoolVar(&stdout, "stdout", false, "print template to stdout instead of writing a file")
	c.Flags().BoolVar(&force, "force", false, "overwrite an existing output file")
	return c
}

func configPrintCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "print",
		Short: "Print effective edge config as YAML",
		RunE: func(cmd *cobra.Command, _ []string) error {
			cfg, _, err := LoadEdgeConfig(cmd)
			if err != nil {
				return err
			}
			enc := yaml.NewEncoder(cmd.OutOrStdout())
			defer enc.Close()
			return enc.Encode(cfg)
		},
	}
}

func configCheckCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "check",
		Short: "Validate edge config can be loaded",
		RunE: func(cmd *cobra.Command, _ []string) error {
			cfg, path, err := LoadEdgeConfig(cmd)
			if err != nil {
				return err
			}
			if err := validateEdgeConfig(cfg); err != nil {
				return fmt.Errorf("validate config: %w", err)
			}
			fmt.Fprintf(cmd.OutOrStdout(), "OK %s\n", path)
			return nil
		},
	}
}

func configRefreshCmd() *cobra.Command {
	var mode string
	var addr string
	var cfgPath string
	c := &cobra.Command{
		Use:   "refresh",
		Short: "Send a config refresh request to a running Edge process",
		Long: `Send a dry-run or apply config refresh request to the Edge-local admin API.
The running Edge process must have refresh.enabled=true in its config.`,
		Example: `  iop-edge config refresh --mode dry-run
  iop-edge config refresh --mode apply --config /path/to/edge.yaml`,
		RunE: func(cmd *cobra.Command, _ []string) error {
			var m configrefresh.Mode
			switch mode {
			case "dry-run", string(configrefresh.ModeDryRun):
				m = configrefresh.ModeDryRun
			case string(configrefresh.ModeApply):
				m = configrefresh.ModeApply
			default:
				return fmt.Errorf("invalid --mode %q: must be dry-run or apply", mode)
			}
			// Resolve config path from flag or parent --config flag.
			if cfgPath == "" {
				resolved, err := ResolveConfigPath(cmd)
				if err != nil {
					return fmt.Errorf("resolve config path: %w", err)
				}
				cfgPath = resolved
			}
			req := configrefresh.Request{
				Mode:       m,
				ConfigPath: cfgPath,
			}
			body, err := json.Marshal(req)
			if err != nil {
				return fmt.Errorf("marshal request: %w", err)
			}
			client := &http.Client{Timeout: 30 * time.Second}
			resp, err := client.Post("http://"+addr+"/refresh", "application/json", bytes.NewReader(body))
			if err != nil {
				return fmt.Errorf("POST http://%s/refresh: %w", addr, err)
			}
			defer resp.Body.Close()
			var result configrefresh.Result
			if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
				return fmt.Errorf("decode response: %w", err)
			}
			enc := json.NewEncoder(cmd.OutOrStdout())
			enc.SetIndent("", "  ")
			return enc.Encode(result)
		},
	}
	c.Flags().StringVar(&mode, "mode", "dry-run", "refresh mode: dry-run or apply")
	c.Flags().StringVar(&addr, "addr", "127.0.0.1:19093", "refresh admin server address")
	c.Flags().StringVar(&cfgPath, "config-path", "", "candidate config path (defaults to the resolved --config path)")
	return c
}

func validateEdgeConfig(cfg *config.EdgeConfig) error {
	return edgevalidate.ValidateEdgeConfig(cfg)
}
