package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

// handleActionRun executes a server action by ID.
// Mirrors: odoo/addons/web/controllers/action.py Action.run()
//
// In Python Odoo this executes ir.actions.server records (Python code, email, etc.).
// In Go we just dispatch to registered methods; for now, return false (no follow-up action).
func (s *Server) handleActionRun(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	var req JSONRPCRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.writeJSONRPC(w, nil, nil, &RPCError{Code: -32700, Message: "Parse error"})
		return
	}
	var params struct {
		ActionID interface{} `json:"action_id"`
		Context  interface{} `json:"context"`
	}
	json.Unmarshal(req.Params, &params)

	// For now, just return false (no follow-up action)
	s.writeJSONRPC(w, req.ID, false, nil)
}

// handleLoadBreadcrumbs returns breadcrumb data for the current navigation path.
// Mirrors: odoo/addons/web/controllers/action.py Action.load_breadcrumbs()
func (s *Server) handleLoadBreadcrumbs(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req JSONRPCRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.writeJSONRPC(w, nil, nil, &RPCError{Code: -32700, Message: "Parse error"})
		return
	}

	s.writeJSONRPC(w, req.ID, []interface{}{}, nil)
}

// handleActionLoad loads an action definition by ID from the database.
// Mirrors: odoo/addons/web/controllers/action.py Action.load()
//
// The action_id can be:
//   - An integer (database ID): SELECT directly from ir_act_window
//   - A string (XML ID like "base.action_res_users"): resolve via ir_model_data first
func (s *Server) handleActionLoad(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req JSONRPCRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		s.writeJSONRPC(w, nil, nil, &RPCError{Code: -32700, Message: "Parse error"})
		return
	}

	var params struct {
		ActionID interface{} `json:"action_id"`
		Context  interface{} `json:"context"`
	}
	json.Unmarshal(req.Params, &params)

	ctx := r.Context()
	var actionID int64

	switch v := params.ActionID.(type) {
	case float64:
		actionID = int64(v)
	case string:
		// Try numeric string first
		if id, ok := parseNumericString(v); ok {
			actionID = id
		} else {
			// XML ID: "module.name" → look up in ir_model_data
			parts := strings.SplitN(v, ".", 2)
			if len(parts) == 2 {
				err := s.pool.QueryRow(ctx,
					`SELECT res_id FROM ir_model_data
					 WHERE module = $1 AND name = $2 AND model = 'ir.actions.act_window'`,
					parts[0], parts[1]).Scan(&actionID)
				if err != nil {
					s.writeJSONRPC(w, req.ID, nil, &RPCError{
						Code:    -32000,
						Message: fmt.Sprintf("Action not found: %s", v),
					})
					return
				}
			}
		}
	}

	if actionID == 0 {
		s.writeJSONRPC(w, req.ID, nil, &RPCError{
			Code:    -32000,
			Message: "Invalid action_id",
		})
		return
	}

	// Load action from ir_act_window table
	var (
		id        int64
		name      string
		actType   string
		resModel  string
		viewMode  string
		resID     *int64
		domain    *string
		actCtx    *string
		target    *string
		limit     *int
		searchVID *string
		help      *string
	)

	err := s.pool.QueryRow(ctx,
		`SELECT id, name, type, res_model, view_mode,
		        res_id, domain, context, target, "limit",
		        search_view_id, help
		 FROM ir_act_window WHERE id = $1`, actionID,
	).Scan(&id, &name, &actType, &resModel, &viewMode,
		&resID, &domain, &actCtx, &target, &limit,
		&searchVID, &help)

	if err != nil {
		s.writeJSONRPC(w, req.ID, nil, &RPCError{
			Code:    -32000,
			Message: fmt.Sprintf("Action %d not found", actionID),
		})
		return
	}

	// Look up xml_id from ir_model_data
	xmlID := ""
	_ = s.pool.QueryRow(ctx,
		`SELECT module || '.' || name FROM ir_model_data
		 WHERE model = 'ir.actions.act_window' AND res_id = $1
		 LIMIT 1`, id).Scan(&xmlID)

	// Build views array from view_mode string (e.g. "list,kanban,form" → [[nil,"list"],[nil,"kanban"],[nil,"form"]])
	views := buildViewsFromMode(viewMode)

	// Assemble action response in the format the webclient expects
	action := map[string]interface{}{
		"id":             id,
		"type":           coalesce(actType, "ir.actions.act_window"),
		"name":           name,
		"res_model":      resModel,
		"view_mode":      coalesce(viewMode, "list,form"),
		"views":          views,
		"search_view_id": false,
		"domain":         coalesce(deref(domain), "[]"),
		"context":        coalesce(deref(actCtx), "{}"),
		"target":         coalesce(deref(target), "current"),
		"limit":          coalesceInt(limit, 80),
		"help":           deref(help),
		"xml_id":         xmlID,
	}

	// Include res_id if set (for single-record actions like Settings)
	if resID != nil && *resID != 0 {
		action["res_id"] = *resID
	}

	s.writeJSONRPC(w, req.ID, action, nil)
}

// parseNumericString tries to parse a string as a positive integer.
func parseNumericString(s string) (int64, bool) {
	if len(s) == 0 {
		return 0, false
	}
	var n int64
	for _, c := range s {
		if c < '0' || c > '9' {
			return 0, false
		}
		n = n*10 + int64(c-'0')
	}
	return n, true
}

// buildViewsFromMode converts a view_mode string like "list,kanban,form"
// into the webclient format: [[nil,"list"],[nil,"kanban"],[nil,"form"]].
func buildViewsFromMode(viewMode string) [][]interface{} {
	modes := strings.Split(viewMode, ",")
	views := make([][]interface{}, 0, len(modes))
	for _, m := range modes {
		m = strings.TrimSpace(m)
		if m != "" {
			views = append(views, []interface{}{nil, m})
		}
	}
	return views
}

// coalesce returns the first non-empty string.
func coalesce(vals ...string) string {
	for _, v := range vals {
		if v != "" {
			return v
		}
	}
	return ""
}

// coalesceInt returns *p if non-nil, otherwise the fallback.
func coalesceInt(p *int, fallback int) int {
	if p != nil {
		return *p
	}
	return fallback
}

// deref returns the value of a string pointer, or "" if nil.
func deref(p *string) string {
	if p != nil {
		return *p
	}
	return ""
}
