//go:build ignore
// +build ignore

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"go.yaml.in/yaml/v3"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
)

const (
	prefix      = "gdev"
	apiVersion  = "scope.grafana.app/v0alpha1"
	defaultURL  = "http://localhost:3000"
	defaultUser = "admin"
)

var (
	grafanaURL  = flag.String("url", getEnv("GRAFANA_URL", defaultURL), "Grafana URL")
	namespace   = flag.String("namespace", getEnv("GRAFANA_NAMESPACE", "default"), "Namespace")
	configFile  = flag.String("config", "scopes-config.yaml", "Config file path")
	user        = flag.String("user", getEnv("GRAFANA_USER", defaultUser), "Grafana username")
	password    = flag.String("password", getEnv("GRAFANA_PASSWORD", "admin"), "Grafana password")
	cleanupFlag = flag.Bool("clean", false, "Delete all gdev-prefixed resources")
)

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

type Config struct {
	Scopes         map[string]ScopeConfig      `yaml:"scopes"`
	Tree           map[string]TreeNode         `yaml:"tree"`
	Navigations    map[string]NavigationConfig `yaml:"navigations"`
	NavigationTree []NavigationTreeNode        `yaml:"navigationTree"`
}

// ScopeConfig is used for YAML parsing - converts to v0alpha1.ScopeSpec
type ScopeConfig struct {
	Title       string              `yaml:"title"`
	DefaultPath []string            `yaml:"defaultPath,omitempty"`
	Filters     []ScopeFilterConfig `yaml:"filters"`
}

// ScopeFilterConfig is used for YAML parsing - converts to v0alpha1.ScopeFilter
type ScopeFilterConfig struct {
	Key      string   `yaml:"key"`
	Value    string   `yaml:"value"`
	Values   []string `yaml:"values,omitempty"`
	Operator string   `yaml:"operator"`
}

// TreeNode is used for YAML parsing - converts to v0alpha1.ScopeNodeSpec
type TreeNode struct {
	Title              string              `yaml:"title"`
	SubTitle           string              `yaml:"subTitle,omitempty"`
	Description        string              `yaml:"description,omitempty"`
	NodeType           string              `yaml:"nodeType"`
	LinkID             string              `yaml:"linkId,omitempty"`
	LinkType           string              `yaml:"linkType,omitempty"`
	DisableMultiSelect bool                `yaml:"disableMultiSelect,omitempty"`
	RedirectPath       string              `yaml:"redirectPath,omitempty"`
	Children           map[string]TreeNode `yaml:"children,omitempty"`
}

type NavigationConfig struct {
	URL                      string   `yaml:"url"`                      // URL path (e.g., /d/abc123 or /explore)
	Scope                    string   `yaml:"scope"`                    // Required scope
	SubScope                 string   `yaml:"subScope"`                 // Optional subScope for hierarchical navigation
	Title                    string   `yaml:"title"`                    // Display title
	Groups                   []string `yaml:"groups"`                   // Optional groups for categorization
	DisableSubScopeSelection bool     `yaml:"disableSubScopeSelection"` // Makes the subscope not selectable
	PreLoadSubScopeChildren  bool     `yaml:"preLoadSubScopeChildren"`  // Preload children of subScope without updating UI
}

// NavigationTreeNode represents a node in the navigation tree structure
type NavigationTreeNode struct {
	Name                     string               `yaml:"name"`
	Title                    string               `yaml:"title"`
	URL                      string               `yaml:"url"`
	Scope                    string               `yaml:"scope"`
	SubScope                 string               `yaml:"subScope,omitempty"`
	Groups                   []string             `yaml:"groups,omitempty"`
	DisableSubScopeSelection bool                 `yaml:"disableSubScopeSelection,omitempty"`
	PreLoadSubScopeChildren  bool                 `yaml:"preLoadSubScopeChildren,omitempty"` // Preload children of subScope without updating UI
	Children                 []NavigationTreeNode `yaml:"children,omitempty"`
}

// Helper function to convert ScopeFilterConfig to v0alpha1.ScopeFilter
func convertFilter(cfg ScopeFilterConfig) v0alpha1.ScopeFilter {
	filter := v0alpha1.ScopeFilter{
		Key:      cfg.Key,
		Value:    cfg.Value,
		Values:   cfg.Values,
		Operator: v0alpha1.FilterOperator(cfg.Operator),
	}
	return filter
}

// Helper function to convert ScopeConfig to v0alpha1.ScopeSpec
func convertScopeSpec(cfg ScopeConfig) v0alpha1.ScopeSpec {
	filters := make([]v0alpha1.ScopeFilter, len(cfg.Filters))
	for i, f := range cfg.Filters {
		filters[i] = convertFilter(f)
	}

	// Prefix defaultPath elements with the gdev prefix
	var defaultPath []string
	if len(cfg.DefaultPath) > 0 {
		defaultPath = make([]string, len(cfg.DefaultPath))
		for i, p := range cfg.DefaultPath {
			defaultPath[i] = prefix + "-" + p
		}
	}

	return v0alpha1.ScopeSpec{
		Title:       cfg.Title,
		DefaultPath: defaultPath,
		Filters:     filters,
	}
}

type Client struct {
	baseURL    string
	namespace  string
	httpClient *http.Client
	auth       string
}

func NewClient(baseURL, namespace, user, password string) *Client {
	return &Client{
		baseURL:    baseURL,
		namespace:  namespace,
		httpClient: &http.Client{},
		auth:       basicAuth(user, password),
	}
}

func basicAuth(username, password string) string {
	return fmt.Sprintf("%s:%s", username, password)
}

func (c *Client) makeRequest(method, endpoint string, body []byte) error {
	url := fmt.Sprintf("%s/apis/%s/namespaces/%s%s", c.baseURL, apiVersion, c.namespace, endpoint)

	var req *http.Request
	var err error

	if body != nil {
		req, err = http.NewRequest(method, url, bytes.NewBuffer(body))
	} else {
		req, err = http.NewRequest(method, url, nil)
	}

	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1])

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		// For DELETE requests, 404 is acceptable (resource already deleted)
		if resp.StatusCode == 404 {
			return nil
		}
		return fmt.Errorf("API request failed: HTTP %d - %s", resp.StatusCode, string(bodyBytes))
	}

	return nil
}

func (c *Client) getScopeNavigation(name string) (*v0alpha1.ScopeNavigation, error) {
	url := fmt.Sprintf("%s/apis/%s/namespaces/%s/scopenavigations/%s", c.baseURL, apiVersion, c.namespace, name)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1])

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("API request failed: HTTP %d - %s", resp.StatusCode, string(bodyBytes))
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	var navigation v0alpha1.ScopeNavigation
	if err := json.Unmarshal(bodyBytes, &navigation); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &navigation, nil
}

func (c *Client) createScope(name string, cfg ScopeConfig) error {
	prefixedName := prefix + "-" + name

	spec := convertScopeSpec(cfg)

	resource := v0alpha1.Scope{
		TypeMeta: metav1.TypeMeta{
			APIVersion: apiVersion,
			Kind:       "Scope",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: prefixedName,
		},
		Spec: spec,
	}

	body, err := json.Marshal(resource)
	if err != nil {
		return fmt.Errorf("failed to marshal scope: %w", err)
	}

	fmt.Printf("✓ Creating scope: %s\n", prefixedName)
	return c.makeRequest("POST", "/scopes", body)
}

func (c *Client) createScopeNode(name string, node TreeNode, parentName string) error {
	prefixedName := prefix + "-" + name
	prefixedParent := ""
	prefixedLinkID := ""

	if parentName != "" {
		prefixedParent = prefix + "-" + parentName
	}

	if node.LinkID != "" {
		prefixedLinkID = prefix + "-" + node.LinkID
	}

	nodeType := v0alpha1.NodeType(node.NodeType)
	if nodeType == "" {
		nodeType = v0alpha1.NodeTypeContainer
	}

	linkType := v0alpha1.LinkType(node.LinkType)
	if linkType == "" {
		linkType = v0alpha1.LinkTypeScope
	}

	spec := v0alpha1.ScopeNodeSpec{
		Title:              node.Title,
		SubTitle:           node.SubTitle,
		Description:        node.Description,
		NodeType:           nodeType,
		DisableMultiSelect: node.DisableMultiSelect,
	}

	if prefixedParent != "" {
		spec.ParentName = prefixedParent
	}

	if prefixedLinkID != "" {
		spec.LinkID = prefixedLinkID
		spec.LinkType = linkType
	}

	if node.RedirectPath != "" {
		spec.RedirectPath = node.RedirectPath
	}

	resource := v0alpha1.ScopeNode{
		TypeMeta: metav1.TypeMeta{
			APIVersion: apiVersion,
			Kind:       "ScopeNode",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: prefixedName,
		},
		Spec: spec,
	}

	body, err := json.Marshal(resource)
	if err != nil {
		return fmt.Errorf("failed to marshal scope node: %w", err)
	}

	fmt.Printf("✓ Creating scope node: %s\n", prefixedName)
	return c.makeRequest("POST", "/scopenodes", body)
}

func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error {
	prefixedName := prefix + "-" + name

	if nav.URL == "" {
		return fmt.Errorf("navigation %s must have 'url' specified", name)
	}

	if nav.Scope == "" {
		return fmt.Errorf("navigation %s must have 'scope' specified", name)
	}

	prefixedScope := prefix + "-" + nav.Scope

	spec := v0alpha1.ScopeNavigationSpec{
		URL:                      nav.URL,
		Scope:                    prefixedScope,
		DisableSubScopeSelection: nav.DisableSubScopeSelection,
		PreLoadSubScopeChildren:  nav.PreLoadSubScopeChildren,
	}

	if nav.SubScope != "" {
		prefixedSubScope := prefix + "-" + nav.SubScope
		spec.SubScope = prefixedSubScope
	}

	status := v0alpha1.ScopeNavigationStatus{
		Title: nav.Title,
	}
	if len(nav.Groups) > 0 {
		status.Groups = nav.Groups
	}

	resource := v0alpha1.ScopeNavigation{
		TypeMeta: metav1.TypeMeta{
			APIVersion: apiVersion,
			Kind:       "ScopeNavigation",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: prefixedName,
		},
		Spec: spec,
	}

	body, err := json.Marshal(resource)
	if err != nil {
		return fmt.Errorf("failed to marshal scope navigation: %w", err)
	}

	fmt.Printf("✓ Creating scope navigation: %s\n", prefixedName)
	if err := c.makeRequest("POST", "/scopenavigations", body); err != nil {
		return err
	}

	// Get the created resource to retrieve its resourceVersion for status update
	createdNav, err := c.getScopeNavigation(prefixedName)
	if err != nil {
		return fmt.Errorf("failed to get created navigation: %w", err)
	}

	// Update status in a second request (status is a subresource)
	if nav.Title != "" || len(nav.Groups) > 0 {
		statusResource := v0alpha1.ScopeNavigation{
			TypeMeta: metav1.TypeMeta{
				APIVersion: apiVersion,
				Kind:       "ScopeNavigation",
			},
			ObjectMeta: metav1.ObjectMeta{
				Name:            prefixedName,
				ResourceVersion: createdNav.ObjectMeta.ResourceVersion,
			},
			Spec:   createdNav.Spec, // Include existing spec to prevent it from being cleared
			Status: status,
		}

		statusBody, err := json.Marshal(statusResource)
		if err != nil {
			return fmt.Errorf("failed to marshal scope navigation status: %w", err)
		}

		fmt.Printf("  Updating status for: %s\n", prefixedName)
		return c.makeRequest("PUT", fmt.Sprintf("/scopenavigations/%s/status", prefixedName), statusBody)
	}

	return nil
}

// NavigationWithName pairs a navigation config with its name and title
type NavigationWithName struct {
	Name  string
	Title string
	Nav   NavigationConfig
}

// Convert navigation tree to flat navigations (similar to mock's treeToNavigations)
func treeToNavigations(node NavigationTreeNode, parentPath []string, dashboardCounter *int) []NavigationWithName {
	navigations := []NavigationWithName{}
	currentPath := append(parentPath, node.Name)

	// Generate URL if not provided (cycle through dash-1, dash-2, etc.)
	url := node.URL
	if url == "" {
		*dashboardCounter++
		url = fmt.Sprintf("/d/dash-%d", *dashboardCounter)
	}

	// Create navigation for this node
	nav := NavigationConfig{
		URL:                      url,
		Scope:                    node.Scope,
		Title:                    node.Title,
		DisableSubScopeSelection: node.DisableSubScopeSelection,
		PreLoadSubScopeChildren:  node.PreLoadSubScopeChildren,
	}
	if node.SubScope != "" {
		nav.SubScope = node.SubScope
	}
	if len(node.Groups) > 0 {
		nav.Groups = node.Groups
	}
	navigations = append(navigations, NavigationWithName{
		Name:  node.Name,
		Title: node.Title,
		Nav:   nav,
	})

	// Process children - they inherit the parent's subScope as their scope, or use parent's scope if no subScope
	if len(node.Children) > 0 {
		for _, child := range node.Children {
			// Children inherit the parent's subScope as their scope, or use parent's scope if no subScope
			childScope := node.SubScope
			if childScope == "" {
				childScope = node.Scope
			}
			// Override with child's scope if explicitly set
			if child.Scope != "" {
				childScope = child.Scope
			} else {
				child.Scope = childScope
			}
			navigations = append(navigations, treeToNavigations(child, currentPath, dashboardCounter)...)
		}
	}

	return navigations
}

func (c *Client) createTreeNodes(children map[string]TreeNode, parentName string) error {
	for name, node := range children {
		// Build full node name by appending to parent name
		// This makes it easy to see the tree path from the node name
		fullNodeName := name
		if parentName != "" {
			fullNodeName = parentName + "-" + name
		}

		// parentName here is the full parent name (already includes full path)
		err := c.createScopeNode(fullNodeName, node, parentName)
		if err != nil {
			return err
		}

		if len(node.Children) > 0 {
			// Pass fullNodeName as parent for children (will be prefixed with "gdev-" in createScopeNode)
			if err := c.createTreeNodes(node.Children, fullNodeName); err != nil {
				return err
			}
		}
	}

	return nil
}

func (c *Client) deleteResources() {
	fmt.Println("Deleting all gdev-prefixed resources...")

	// Delete scopes (silently handle errors if endpoints aren't available)
	c.deleteResourceType("/scopes", "scope")

	// Delete scope nodes
	c.deleteResourceType("/scopenodes", "scope node")

	// Delete scope navigations
	c.deleteResourceType("/scopenavigations", "scope navigation")

	fmt.Println("✓ Cleanup complete")
}

func (c *Client) deleteResourceType(endpoint, resourceType string) {
	url := fmt.Sprintf("%s/apis/%s/namespaces/%s%s", c.baseURL, apiVersion, c.namespace, endpoint)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		// Silently skip if we can't create request
		return
	}

	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1])

	resp, err := c.httpClient.Do(req)
	if err != nil {
		// Silently skip if endpoint isn't available
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		// Silently skip if endpoint returns error (might not be available)
		return
	}

	var listResponse struct {
		Items []struct {
			Metadata struct {
				Name string `json:"name"`
			} `json:"metadata"`
		} `json:"items"`
	}

	bodyBytes, _ := io.ReadAll(resp.Body)
	if err := json.Unmarshal(bodyBytes, &listResponse); err != nil {
		// Silently skip if we can't decode response
		return
	}

	if len(listResponse.Items) == 0 {
		return
	}

	deletedCount := 0
	for _, item := range listResponse.Items {
		if strings.HasPrefix(item.Metadata.Name, prefix+"-") {
			fmt.Printf("  Deleting %s: %s\n", resourceType, item.Metadata.Name)
			deleteURL := fmt.Sprintf("%s/%s", endpoint, item.Metadata.Name)
			if err := c.makeRequest("DELETE", deleteURL, nil); err != nil {
				// Silently skip deletion errors
			} else {
				deletedCount++
			}
		}
	}
}

func main() {
	flag.Parse()

	client := NewClient(*grafanaURL, *namespace, *user, *password)

	if *cleanupFlag {
		// Cleanup should be silent if endpoints aren't available
		client.deleteResources()
		return
	}

	configData, err := os.ReadFile(*configFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error reading config file: %v\n", err)
		os.Exit(1)
	}

	var config Config
	if err := yaml.Unmarshal(configData, &config); err != nil {
		fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Loading configuration from: %s\n", *configFile)
	fmt.Printf("Grafana URL: %s\n", *grafanaURL)
	fmt.Printf("Namespace: %s\n", *namespace)
	fmt.Printf("Prefix: %s\n\n", prefix)

	// Create scopes
	fmt.Println("Creating scopes...")
	for name, scope := range config.Scopes {
		if err := client.createScope(name, scope); err != nil {
			fmt.Fprintf(os.Stderr, "Error creating scope %s: %v\n", name, err)
			os.Exit(1)
		}
	}
	fmt.Println()

	// Create scope nodes (tree structure)
	if len(config.Tree) > 0 {
		fmt.Println("Creating scope nodes...")
		if err := client.createTreeNodes(config.Tree, ""); err != nil {
			fmt.Fprintf(os.Stderr, "Error creating scope nodes: %v\n", err)
			os.Exit(1)
		}
		fmt.Println()
	}

	// Create scope navigations
	// First, process navigation tree if provided
	if len(config.NavigationTree) > 0 {
		fmt.Println("Creating scope navigations from tree...")
		dashboardCounter := 0
		for _, rootNode := range config.NavigationTree {
			flatNavigations := treeToNavigations(rootNode, []string{}, &dashboardCounter)
			for _, navWithName := range flatNavigations {
				// Use the title from navWithName if Nav.Title is empty
				if navWithName.Nav.Title == "" && navWithName.Title != "" {
					navWithName.Nav.Title = navWithName.Title
				}
				if err := client.createScopeNavigation(navWithName.Name, navWithName.Nav); err != nil {
					fmt.Fprintf(os.Stderr, "Error creating scope navigation %s: %v\n", navWithName.Name, err)
					os.Exit(1)
				}
			}
		}
		fmt.Println()
	}

	// Also support flat navigations format for backward compatibility
	if len(config.Navigations) > 0 {
		fmt.Println("Creating scope navigations...")
		for name, nav := range config.Navigations {
			if err := client.createScopeNavigation(name, nav); err != nil {
				fmt.Fprintf(os.Stderr, "Error creating scope navigation %s: %v\n", name, err)
				os.Exit(1)
			}
		}
		fmt.Println()
	}

	fmt.Println("✓ All resources created successfully!")
}
