package api

import (
	"errors"
	"strings"

	"github.com/gofiber/fiber/v2"

	"fiko-ai/internal/protocol"
	"fiko-ai/internal/tool"
)

// toolView liste/tekil yanıt için tool + o anki kullanıcıya ilişkin çalıştırılabilirlik bilgisi.
// Arayüz "Çalıştır" düğmesini doğru göstermek için bunu bilmeli.
type toolView struct {
	tool.Tool
	Executable bool     `json:"executable"`        // çalışma zamanı uygulaması bağlı mı
	Allowed    bool     `json:"allowed"`           // bu kullanıcı yetkili mi
	Missing    []string `json:"missing,omitempty"` // eksik izinler (yalnızca allowed=false ise)
	// Tool.UI burada "auto" DEĞİL, çözülmüş değerle dolar (bkz. viewTool).
	// Arayüz "hangi UI" kararını bu alandan okur; istemci türetme yapmaz.
}

type toolResponse struct {
	Tool toolView `json:"tool"`
}

type toolsResponse struct {
	Items  []toolView `json:"items"`
	Count  int        `json:"count"`
	Limit  int        `json:"limit"`
	Offset int        `json:"offset"`
}

type toolValidateResponse struct {
	Valid  bool              `json:"valid"`
	Errors []tool.FieldError `json:"errors"` // geçerliyse boş dizi (null değil)
}

type toolStatusRequest struct {
	Status tool.Status `json:"status"`
}

type toolExecuteRequest struct {
	Input map[string]any `json:"input"`
}

// toolsAvailable Tool Registry yapılandırılmamışsa tüm /api/tools uçlarını 503 yapar.
func (s *Server) toolsAvailable(c *fiber.Ctx) error {
	if s.deps.Tools == nil || s.deps.ToolExec == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal, "tool sistemi yapılandırılmamış")
	}
	return c.Next()
}

// listTools GET /api/tools?status=&kind=&risk=&tag=&q=&limit=&offset= — ada göre sıralı.
func (s *Server) listTools(c *fiber.Ctx) error {
	limit, offset, err := s.parsePaging(c)
	if err != nil {
		return err
	}
	status := tool.Status(strings.TrimSpace(c.Query("status")))
	if status != "" && !status.Valid() {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "status enabled veya disabled olmalı")
	}
	kind := tool.Kind(strings.TrimSpace(c.Query("kind")))
	if kind != "" && !kind.Valid() {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "kind builtin veya custom olmalı")
	}
	risk := tool.Risk(strings.TrimSpace(c.Query("risk")))
	if risk != "" && !risk.Valid() {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "risk low, medium, high veya critical olmalı")
	}
	f := tool.ListFilter{
		Status: status,
		Kind:   kind,
		Risk:   risk,
		Tag:    strings.ToLower(strings.TrimSpace(c.Query("tag"))),
		Query:  c.Query("q"),
	}
	items, err := s.deps.Tools.List(c.UserContext(), f, limit, offset)
	if err != nil {
		return failDomain(c, err)
	}
	views := s.viewTools(s.currentUser(c), items)
	return c.JSON(toolsResponse{Items: views, Count: len(views), Limit: limit, Offset: offset})
}

// getTool GET /api/tools/:id
func (s *Server) getTool(c *fiber.Ctx) error {
	t, err := s.deps.Tools.Get(c.UserContext(), c.Params("id"))
	if err != nil {
		return failDomain(c, err)
	}
	return c.JSON(toolResponse{Tool: s.viewTool(s.currentUser(c), *t)})
}

// createTool POST /api/tools — kimlik ve zaman damgaları sunucuya aittir.
func (s *Server) createTool(c *fiber.Ctx) error {
	in, err := parseToolBody(c)
	if err != nil {
		return err
	}
	return s.writeTool(c, "tool.create", fiber.StatusCreated, func() (*tool.Tool, error) {
		return s.deps.Tools.Register(c.UserContext(), in)
	})
}

// updateTool PUT /api/tools/:id — tool'u gövdedeki alanlarla TAMAMEN değiştirir.
func (s *Server) updateTool(c *fiber.Ctx) error {
	in, err := parseToolBody(c)
	if err != nil {
		return err
	}
	id := c.Params("id")
	return s.writeTool(c, "tool.update", fiber.StatusOK, func() (*tool.Tool, error) {
		return s.deps.Tools.Update(c.UserContext(), id, in)
	})
}

// setToolStatus PUT /api/tools/:id/status {"status":"disabled"}
func (s *Server) setToolStatus(c *fiber.Ctx) error {
	var in toolStatusRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
	}
	if strings.TrimSpace(string(in.Status)) == "" {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "status gerekli")
	}
	id := c.Params("id")
	return s.writeTool(c, "tool.status", fiber.StatusOK, func() (*tool.Tool, error) {
		return s.deps.Tools.SetStatus(c.UserContext(), id, in.Status)
	})
}

// deleteTool DELETE /api/tools/:id — builtin tool'lar silinemez (409).
func (s *Server) deleteTool(c *fiber.Ctx) error {
	id, userID := c.Params("id"), s.currentUser(c)
	if err := s.deps.Tools.Delete(c.UserContext(), id); err != nil {
		s.auditFailure(userID, "tool.delete", id+": "+err.Error())
		return failDomain(c, err)
	}
	s.auditSuccess(userID, "tool.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// validateTool POST /api/tools/validate[?id=<toolId>] — kaydetmeden doğrular.
// Geçersizlik hata değil SONUÇTUR: her zaman 200 döner.
func (s *Server) validateTool(c *fiber.Ctx) error {
	in, err := parseToolBody(c)
	if err != nil {
		return err
	}
	errs, err := s.deps.Tools.Validate(c.UserContext(), strings.TrimSpace(c.Query("id")), in)
	if err != nil {
		return failDomain(c, err)
	}
	return c.JSON(toolValidateResponse{Valid: len(errs) == 0, Errors: errs})
}

// executeTool POST /api/tools/:id/execute {"input":{...}}
//
// Bu uç "Test" düğmesidir: tanımın gerçekten çalıştığını elle sınamak içindir.
// Yetki denetimi yine Executor'da yapılır (kullanıcının izni yoksa 403).
func (s *Server) executeTool(c *fiber.Ctx) error {
	var in toolExecuteRequest
	if len(c.Body()) > 0 {
		if err := c.BodyParser(&in); err != nil {
			return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
		}
	}
	userID := s.currentUser(c)
	res, err := s.deps.ToolExec.Execute(c.UserContext(), tool.Request{
		Ref: c.Params("id"), Input: in.Input, Actor: "user", UserID: userID,
		ConversationID: strings.TrimSpace(c.Query("conversationId")),
	})
	if err != nil {
		return failDomain(c, err)
	}
	return c.JSON(res)
}

// executeSkill POST /api/skills/:id/execute {"arguments":{...}}
//
// Skill'in executorConfig.tool bağını gerçekten çalıştırır (PROJE.MD: ExecuteSkill).
func (s *Server) executeSkill(c *fiber.Ctx) error {
	var in struct {
		Arguments      map[string]any `json:"arguments"`
		ConversationID string         `json:"conversationId"`
	}
	if len(c.Body()) > 0 {
		if err := c.BodyParser(&in); err != nil {
			return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "geçersiz istek gövdesi")
		}
	}
	userID, skillID := s.currentUser(c), c.Params("id")
	if _, err := s.deps.Skills.Get(c.UserContext(), skillID); err != nil {
		return failDomain(c, err)
	}
	res, err := s.deps.Skills.ExecuteSkill(c.UserContext(), skillID, in.Arguments)
	if err != nil {
		s.auditFailure(userID, "skill.execute", skillID+": "+err.Error())
		return failDomain(c, err)
	}
	s.auditSuccess(userID, "skill.execute", "skill="+res.SkillName+" tool="+res.Tool)
	return c.JSON(res)
}

// writeTool yazma işlemini çalıştırır, denetim kaydı alır ve tool'u yanıtlar.
func (s *Server) writeTool(c *fiber.Ctx, action string, status int, op func() (*tool.Tool, error)) error {
	userID := s.currentUser(c)
	t, err := op()
	if err != nil {
		s.auditFailure(userID, action, err.Error())
		return failDomain(c, err)
	}
	s.auditSuccess(userID, action, "name="+t.Name+" id="+t.ID)
	return c.Status(status).JSON(toolResponse{Tool: s.viewTool(userID, *t)})
}

// viewTools liste yanıtını hazırlar.
func (s *Server) viewTools(userID string, items []tool.Tool) []toolView {
	out := make([]toolView, 0, len(items))
	for _, t := range items {
		out = append(out, s.viewTool(userID, t))
	}
	return out
}

// viewTool tek tool'u kullanıcı perspektifiyle döndürür.
//
// UI alanı burada "auto" değil, ÇÖZÜLMÜŞ değerle döner: arayüz hangi görünümü
// göstereceğini bu alandan okur; "auto"u görürse yanlış bileşen seçebilirdi.
func (s *Server) viewTool(userID string, t tool.Tool) toolView {
	policy := s.deps.Grants.PolicyFor(userID)
	t.UI = tool.ResolveUI(&t)
	v := toolView{Tool: t, Executable: s.deps.Tools.ExecutableTool(&t)}
	if err := policy.Authorize(&t); err != nil {
		v.Missing = missingOf(policy, &t, err)
		return v
	}
	v.Allowed = true
	return v
}

// missingOf yetki hatasından arayüzün gösterileceği eksikleri çıkarır.
func missingOf(p tool.Policy, t *tool.Tool, err error) []string {
	var pe *tool.PermissionError
	if !errors.As(err, &pe) {
		return nil
	}
	if pe.Risk != "" {
		// İzinler tamam, risk kapısı kapalı: arayüz "risk onayı" göstermeli.
		if p.Grants.Missing(tool.Required(t)) != tool.PermNone {
			return p.Grants.Missing(tool.Required(t)).Names()
		}
		return []string{"risk:" + string(pe.Risk)}
	}
	return pe.Missing.Names()
}

// parseToolBody gövdeyi Tool'a çevirir. Hata *fiber.Error'dür (handler `return err` ile bırakır).
func parseToolBody(c *fiber.Ctx) (tool.Tool, error) {
	var in tool.Tool
	if err := c.BodyParser(&in); err != nil {
		return tool.Tool{}, fiber.NewError(fiber.StatusBadRequest, "geçersiz istek gövdesi")
	}
	return in, nil
}
