package cmd

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

	"github.com/gorilla/websocket"
	"github.com/spf13/cobra"

	"github.com/nextlevelbuilder/goclaw/internal/config"
	"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)

func pairingCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "pairing",
		Short: "Manage device pairing (approve, list, revoke)",
	}

	cmd.AddCommand(pairingApproveCmd())
	cmd.AddCommand(pairingListCmd())
	cmd.AddCommand(pairingRevokeCmd())

	return cmd
}

func pairingApproveCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "approve [code]",
		Short: "Approve a pairing code (interactive if no code given)",
		Args:  cobra.MaximumNArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			var code string
			if len(args) == 1 {
				code = args[0]
			} else {
				code = pairingInteractiveSelect()
				if code == "" {
					return
				}
			}

			pairingApproveByCode(code)
		},
	}
}

// pairingInteractiveSelect fetches pending pairings from the gateway and lets the user pick one.
func pairingInteractiveSelect() string {
	fmt.Println("Fetching pending pairings...")
	fmt.Println()

	resp, err := gatewayRPC(protocol.MethodPairingList, nil)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}
	if !resp.OK {
		fmt.Printf("Failed: %s\n", resp.Error.Message)
		os.Exit(1)
	}

	// Parse the payload into pending list
	raw, err := json.Marshal(resp.Payload)
	if err != nil {
		fmt.Printf("Error parsing response: %v\n", err)
		os.Exit(1)
	}

	var listResult struct {
		Pending []struct {
			Code      string `json:"code"`
			SenderID  string `json:"sender_id"`
			Channel   string `json:"channel"`
			ChatID    string `json:"chat_id"`
			AccountID string `json:"account_id"`
			CreatedAt int64  `json:"created_at"`
			ExpiresAt int64  `json:"expires_at"`
		} `json:"pending"`
	}
	if err := json.Unmarshal(raw, &listResult); err != nil {
		fmt.Printf("Error parsing pairing list: %v\n", err)
		os.Exit(1)
	}

	if len(listResult.Pending) == 0 {
		fmt.Println("No pending pairing requests.")
		return ""
	}

	fmt.Printf("Found %d pending request(s).\n\n", len(listResult.Pending))

	// Build select options
	options := make([]SelectOption[string], 0, len(listResult.Pending))
	for _, p := range listResult.Pending {
		createdAt := time.UnixMilli(p.CreatedAt)
		ago := time.Since(createdAt).Truncate(time.Second)
		label := fmt.Sprintf("[%s]  %s / %s  (%s ago)", p.Code, p.Channel, p.SenderID, ago)
		options = append(options, SelectOption[string]{Label: label, Value: p.Code})
	}

	selected, err := promptSelect("Select a pairing request to approve", options, 0)
	if err != nil {
		fmt.Println("Cancelled.")
		return ""
	}

	return selected
}

func pairingApproveByCode(code string) {
	params, _ := json.Marshal(map[string]string{
		"code":       code,
		"approvedBy": "cli-operator",
	})

	resp, err := gatewayRPC(protocol.MethodPairingApprove, params)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}

	if !resp.OK {
		fmt.Printf("Failed: %s\n", resp.Error.Message)
		os.Exit(1)
	}

	fmt.Printf("Pairing approved! Code: %s\n", code)

	if resp.Payload != nil {
		data, _ := json.MarshalIndent(resp.Payload, "", "  ")
		fmt.Println(string(data))
	}
}

func pairingListCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "list",
		Short: "List pending and paired devices",
		Run: func(cmd *cobra.Command, args []string) {
			resp, err := gatewayRPC(protocol.MethodPairingList, nil)
			if err != nil {
				fmt.Printf("Error: %v\n", err)
				os.Exit(1)
			}

			if !resp.OK {
				fmt.Printf("Failed: %s\n", resp.Error.Message)
				os.Exit(1)
			}

			data, _ := json.MarshalIndent(resp.Payload, "", "  ")
			fmt.Println(string(data))
		},
	}
}

func pairingRevokeCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "revoke <channel> <senderId>",
		Short: "Revoke a paired device",
		Args:  cobra.ExactArgs(2),
		Run: func(cmd *cobra.Command, args []string) {
			params, _ := json.Marshal(map[string]string{
				"channel":  args[0],
				"senderId": args[1],
			})

			resp, err := gatewayRPC(protocol.MethodPairingRevoke, params)
			if err != nil {
				fmt.Printf("Error: %v\n", err)
				os.Exit(1)
			}

			if !resp.OK {
				fmt.Printf("Failed: %s\n", resp.Error.Message)
				os.Exit(1)
			}

			fmt.Printf("Revoked pairing for %s/%s\n", args[0], args[1])
		},
	}
}

// gatewayRPC connects to the running gateway, authenticates, sends an RPC call, and returns the response.
func gatewayRPC(method string, params json.RawMessage) (*protocol.ResponseFrame, error) {
	cfg, err := config.Load(resolveConfigPath())
	if err != nil {
		return nil, fmt.Errorf("load config: %w", err)
	}

	host := cfg.Gateway.Host
	if host == "0.0.0.0" {
		host = "127.0.0.1"
	}

	u := url.URL{Scheme: "ws", Host: fmt.Sprintf("%s:%d", host, cfg.Gateway.Port), Path: "/ws"}
	conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("connect to gateway at %s: %w", u.String(), err)
	}
	defer conn.Close()

	// Step 1: Send connect handshake
	connectParams, _ := json.Marshal(map[string]any{
		"token":    cfg.Gateway.Token,
		"protocol": protocol.ProtocolVersion,
	})
	connectReq := protocol.RequestFrame{
		Type:   protocol.FrameTypeRequest,
		ID:     "cli-connect",
		Method: protocol.MethodConnect,
		Params: connectParams,
	}
	if err := conn.WriteJSON(connectReq); err != nil {
		return nil, fmt.Errorf("send connect: %w", err)
	}

	// Read connect response
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	var connectResp protocol.ResponseFrame
	if err := conn.ReadJSON(&connectResp); err != nil {
		return nil, fmt.Errorf("read connect response: %w", err)
	}
	if !connectResp.OK {
		msg := "unknown error"
		if connectResp.Error != nil {
			msg = connectResp.Error.Message
		}
		return nil, fmt.Errorf("connect failed: %s", msg)
	}

	// Step 2: Send the RPC call
	rpcReq := protocol.RequestFrame{
		Type:   protocol.FrameTypeRequest,
		ID:     "cli-rpc",
		Method: method,
		Params: params,
	}
	if err := conn.WriteJSON(rpcReq); err != nil {
		return nil, fmt.Errorf("send RPC: %w", err)
	}

	// Read response (skip events, find response with matching ID)
	conn.SetReadDeadline(time.Now().Add(10 * time.Second))
	for {
		_, msg, err := conn.ReadMessage()
		if err != nil {
			return nil, fmt.Errorf("read response: %w", err)
		}

		frameType, _ := protocol.ParseFrameType(msg)
		if frameType == protocol.FrameTypeEvent {
			continue // skip events
		}

		var resp protocol.ResponseFrame
		if err := json.Unmarshal(msg, &resp); err != nil {
			return nil, fmt.Errorf("parse response: %w", err)
		}
		if resp.ID == "cli-rpc" {
			return &resp, nil
		}
	}
}
