package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"git.warky.dev/wdevs/whatshooked/pkg/models"
	"git.warky.dev/wdevs/whatshooked/pkg/storage"
	resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
	"github.com/google/uuid"
	"github.com/spf13/cobra"
)

var hookUser string

var hooksCmd = &cobra.Command{
	Use:   "hooks",
	Short: "Manage webhooks",
	Run: func(cmd *cobra.Command, args []string) {
		client := NewClient(cliConfig)
		listHooks(client)
	},
}

var hooksListCmd = &cobra.Command{
	Use:   "list",
	Short: "List all hooks",
	Run: func(cmd *cobra.Command, args []string) {
		client := NewClient(cliConfig)
		listHooks(client)
	},
}

var hooksAddCmd = &cobra.Command{
	Use:   "add",
	Short: "Add a new hook",
	Run: func(cmd *cobra.Command, args []string) {
		client := NewClient(cliConfig)
		addHook(client)
	},
}

var hooksRemoveCmd = &cobra.Command{
	Use:   "remove <hook_id>",
	Short: "Remove a hook by ID",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		client := NewClient(cliConfig)
		removeHook(client, args[0])
	},
}

func init() {
	hooksAddCmd.Flags().StringVarP(&hookUser, "user", "u", "", "Owner username for DB mode (default: first admin)")
	hooksCmd.AddCommand(hooksListCmd)
	hooksCmd.AddCommand(hooksAddCmd)
	hooksCmd.AddCommand(hooksRemoveCmd)
}

func listHooks(client *Client) {
	if serverAvailable(client) {
		listHooksHTTP(client)
	} else {
		fmt.Println("[server unavailable, reading from database]")
		if !tryInitDB() {
			fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.")
			return
		}
		listHooksDB()
	}
}

func listHooksHTTP(client *Client) {
	resp, err := client.Get("/api/hooks")
	checkError(err)
	defer resp.Body.Close()

	var hooks []map[string]interface{}
	checkError(decodeJSON(resp, &hooks))

	if len(hooks) == 0 {
		fmt.Println("No hooks configured")
		return
	}

	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "ID\tNAME\tURL\tMETHOD\tACTIVE")
	for _, h := range hooks {
		fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n",
			h["id"], h["name"], h["url"], h["method"], h["active"])
	}
	w.Flush()
}

func listHooksDB() {
	var hooks []models.ModelPublicHook
	err := storage.DB.NewSelect().Model(&hooks).OrderExpr("created_at ASC").Scan(context.Background())
	checkError(err)

	if len(hooks) == 0 {
		fmt.Println("No hooks configured")
		return
	}

	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "ID\tNAME\tURL\tMETHOD\tACTIVE\tEVENTS")
	for _, h := range hooks {
		active := "yes"
		if !h.Active {
			active = "no"
		}
		events := parseEventsJSON(h.Events.String())
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
			h.ID.String(),
			h.Name.String(),
			h.URL.String(),
			h.Method.String(),
			active,
			events,
		)
	}
	w.Flush()
}

func addHook(client *Client) {
	name := promptRequired("Hook Name")
	url := promptRequired("Webhook URL")
	method := promptLine("HTTP Method", "POST")

	fmt.Println("\nAvailable events:")
	fmt.Println("  whatsapp.connected, whatsapp.disconnected, whatsapp.qr.code")
	fmt.Println("  message.received, message.sent, message.delivered, message.read")
	fmt.Println("  hook.triggered, hook.success, hook.failed")
	eventsRaw := promptLine("\nEvents (comma-separated, or Enter for all)", "")
	description := promptLine("Description (optional)", "")

	var events []string
	if eventsRaw != "" {
		for _, e := range strings.Split(eventsRaw, ",") {
			if t := strings.TrimSpace(e); t != "" {
				events = append(events, t)
			}
		}
	}

	if serverAvailable(client) {
		addHookHTTP(client, name, url, method, description, events)
	} else {
		fmt.Println("[server unavailable, writing to database]")
		if !tryInitDB() {
			fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.")
			return
		}
		addHookDB(name, url, method, description, events)
	}
}

func addHookHTTP(client *Client, name, url, method, description string, events []string) {
	payload := map[string]interface{}{
		"name":        name,
		"url":         url,
		"method":      method,
		"description": description,
		"events":      events,
		"active":      true,
	}
	resp, err := client.Post("/api/hooks/add", payload)
	checkError(err)
	defer resp.Body.Close()
	fmt.Println("Hook added successfully")
}

func addHookDB(name, url, method, description string, events []string) {
	userID := dbOwnerUserID(hookUser)
	if userID == "" {
		fmt.Println("Error: no users found in database. Create a user first with: users add")
		return
	}

	eventsJSON := "[]"
	if len(events) > 0 {
		b, _ := json.Marshal(events)
		eventsJSON = string(b)
	}

	id := uuid.New().String()
	now := time.Now()
	hook := &models.ModelPublicHook{
		ID:          resolvespec_common.NewSqlString(id),
		Name:        resolvespec_common.NewSqlString(name),
		URL:         resolvespec_common.NewSqlString(url),
		Method:      resolvespec_common.NewSqlString(method),
		Description: resolvespec_common.NewSqlString(description),
		Events:      resolvespec_common.NewSqlString(eventsJSON),
		UserID:      resolvespec_common.NewSqlString(userID),
		Active:      true,
		CreatedAt:   resolvespec_common.NewSqlTimeStamp(now),
		UpdatedAt:   resolvespec_common.NewSqlTimeStamp(now),
	}

	repo := storage.NewHookRepository(storage.DB)
	checkError(repo.Create(context.Background(), hook))

	fmt.Printf("Hook '%s' added (ID: %s)\n", name, id)
}

func removeHook(client *Client, id string) {
	if serverAvailable(client) {
		resp, err := client.Post("/api/hooks/remove", map[string]string{"id": id})
		checkError(err)
		defer resp.Body.Close()
		fmt.Println("Hook removed successfully")
	} else {
		fmt.Println("[server unavailable, removing from database]")
		if !tryInitDB() {
			fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.")
			return
		}
		repo := storage.NewHookRepository(storage.DB)
		checkError(repo.Delete(context.Background(), id))
		fmt.Printf("Hook '%s' removed\n", id)
	}
}

// parseEventsJSON parses a JSON events string into a comma-separated display string.
func parseEventsJSON(raw string) string {
	if raw == "" || raw == "[]" {
		return "all"
	}
	var events []string
	if err := json.Unmarshal([]byte(raw), &events); err != nil {
		return raw
	}
	return strings.Join(events, ", ")
}
