package api

import (
	"context"
	"errors"
	"log/slog"

	"github.com/gofiber/fiber/v2"

	"fiko-ai/internal/conversation"
	"fiko-ai/internal/llm"
	"fiko-ai/internal/persona"
	"fiko-ai/internal/protocol"
	"fiko-ai/internal/skill"
	"fiko-ai/internal/tool"
	"fiko-ai/internal/workflow"
)

// errorBody tüm hata yanıtlarının ortak şeklidir (WS hata olayıyla aynı alan adları).
type errorBody struct {
	Error apiError `json:"error"`
}

// fieldError alan bazlı hataların ortak biçimidir (skill.FieldError ve tool.FieldError
// aynı JSON sözleşmesine sahiptir; tek tipe indirgemek dönüşümü sadeleştirir).
type fieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

type apiError struct {
	Code    string       `json:"code"`
	Message string       `json:"message"`
	Fields  []fieldError `json:"fields,omitempty"` // yalnızca validation_error: alan bazlı hatalar
}

// fail hata gövdesiyle yanıt üretir ve fiber hatası döner; böylece tek yerde loglanır.
func fail(c *fiber.Ctx, status int, code, msg string) error {
	return c.Status(status).JSON(errorBody{Error: apiError{Code: code, Message: msg}})
}

// failDomain servis hatasını HTTP durumuna çevirir.
func failDomain(c *fiber.Ctx, err error) error {
	var ve *skill.ValidationError
	var tv *tool.ValidationError
	var ie *tool.InputError
	var fe *tool.FailedError
	switch {
	case errors.As(err, &ve):
		return c.Status(fiber.StatusBadRequest).JSON(errorBody{Error: apiError{
			Code: protocol.ErrCodeValidation, Message: "skill geçersiz", Fields: skillFields(ve.Errors),
		}})
	case errors.As(err, &tv):
		return c.Status(fiber.StatusBadRequest).JSON(errorBody{Error: apiError{
			Code: protocol.ErrCodeValidation, Message: "tool geçersiz", Fields: toolFields(tv.Errors),
		}})
	case errors.As(err, &ie):
		// Girdi doğrulaması: tanım sağlam, çağrı yanlış. 400 + alan bazlı hatalar.
		return c.Status(fiber.StatusBadRequest).JSON(errorBody{Error: apiError{
			Code: protocol.ErrCodeValidation, Message: ie.Error(), Fields: toolFields(ie.Errors),
		}})
	case errors.Is(err, tool.ErrPermissionDenied):
		// Yetki reddi 403: kullanıcı kimliği GEÇERLİ ama izni yok.
		return fail(c, fiber.StatusForbidden, protocol.ErrCodePermissionDenied, "bu tool için yetkiniz yok")
	case errors.Is(err, tool.ErrDisabled):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "tool devre dışı bırakılmış")
	case errors.Is(err, tool.ErrNotExecutable):
		return fail(c, fiber.StatusNotImplemented, protocol.ErrCodeNotImplemented,
			"bu tool için çalıştırılabilir uygulama yok (yalnızca tanımı kayıtlı)")
	case errors.Is(err, tool.ErrBuiltin):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict,
			"yerleşik tool silinemez; kapatabilirsiniz")
	case errors.As(err, &fe):
		return fail(c, fiber.StatusBadGateway, protocol.ErrCodeInternal, fe.Error())
	case errors.Is(err, tool.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "tool bulunamadı")
	case errors.Is(err, tool.ErrDuplicateName):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "bu ada sahip bir tool zaten var")
	case errors.Is(err, skill.ErrNotExecutable):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "skill çalıştırılamaz durumda")
	case errors.Is(err, skill.ErrNoExecutor):
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal, "tool sistemi bağlı değil")
	case errors.Is(err, skill.ErrExecutorUnsupported):
		return fail(c, fiber.StatusNotImplemented, protocol.ErrCodeNotImplemented, "bu yürütücü henüz desteklenmiyor")
	case errors.Is(err, skill.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "skill bulunamadı")
	case errors.Is(err, skill.ErrDuplicateName):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "bu ada sahip bir skill zaten var")
	case errors.Is(err, conversation.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "konuşma bulunamadı")
	case errors.Is(err, conversation.ErrEmptyContent):
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "mesaj boş olamaz")
	case errors.Is(err, context.DeadlineExceeded):
		return fail(c, fiber.StatusGatewayTimeout, protocol.ErrCodeLLM, "istek zaman aşımına uğradı")
	case errors.Is(err, context.Canceled):
		return fail(c, 499, protocol.ErrCodeBadRequest, "istek iptal edildi")
	default:
		return err // bilinmeyen hata: errorHandler 500 üretir ve loglar
	}
}

// skillFields / toolFields, iki paketin alan hata tiplerini API'nin ortak biçimine çevirir.
// JSON sözleşmesi değişmez: {field, message}.
func skillFields(in []skill.FieldError) []fieldError {
	out := make([]fieldError, 0, len(in))
	for _, e := range in {
		out = append(out, fieldError{Field: e.Field, Message: e.Message})
	}
	return out
}

func toolFields(in []tool.FieldError) []fieldError {
	out := make([]fieldError, 0, len(in))
	for _, e := range in {
		out = append(out, fieldError{Field: e.Field, Message: e.Message})
	}
	return out
}

// workflowFields, akış paketinin alan hata tiplerini API'nin ortak biçimine çevirir
// (JSON sözleşmesi aynı: {field, message}).
func workflowFields(in []workflow.FieldError) []fieldError {
	out := make([]fieldError, 0, len(in))
	for _, e := range in {
		out = append(out, fieldError{Field: e.Field, Message: e.Message})
	}
	return out
}

// personaFields, kullanıcı profili paketinin alan hatalarını ortak biçime çevirir.
// Arayüz alan altında hata gösterir (name/instructions/notes).
func personaFields(in []persona.FieldError) []fieldError {
	out := make([]fieldError, 0, len(in))
	for _, e := range in {
		out = append(out, fieldError{Field: e.Field, Message: e.Message})
	}
	return out
}

// failWorkflow akış servisinin hatalarını HTTP durumuna çevirir.
// NotRunnable ayrımı korunur: akış KAYDEDİLEBİLİR ama ÇALIŞTIRILAMAZ (409), çünkü
// 400 dönse kullanıcı "girmezsem olmaz" sanırdı; mesaj bunu açıkça söyler.
func failWorkflow(c *fiber.Ctx, err error) error {
	var ve *workflow.ValidationError
	var ce *workflow.CapabilityError
	switch {
	case errors.As(err, &ve):
		status := fiber.StatusBadRequest
		msg := "akış geçersiz"
		if ve.NotRunnable {
			status = fiber.StatusConflict
			msg = "akış kaydedilebilir ama çalıştırılamaz"
		}
		return c.Status(status).JSON(errorBody{Error: apiError{
			Code: protocol.ErrCodeValidation, Message: msg, Fields: workflowFields(ve.Errors),
		}})
	case errors.As(err, &ce):
		// Sunucuda gerekli yetenek bağlı değil (ör. LLM yokken llm node'u): istemci hatası değil.
		return c.Status(fiber.StatusNotImplemented).JSON(errorBody{Error: apiError{
			Code: protocol.ErrCodeNotImplemented, Message: ce.Error(),
		}})
	case errors.Is(err, workflow.ErrBusy):
		return fail(c, fiber.StatusTooManyRequests, protocol.ErrCodeConflict, err.Error())
	case errors.Is(err, workflow.ErrClosed):
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal, err.Error())
	case errors.Is(err, workflow.ErrAlreadyFinished):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, "yürütme zaten bitmiş")
	case errors.Is(err, workflow.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "akış ya da yürütme bulunamadı")
	default:
		return err // bilinmeyen hata: errorHandler 500 üretir ve loglar
	}
}

// errorHandler Fiber'ın ürettiği hataları (404, 405, 413, panic dışı fiber hataları) JSON'a çevirir.
func errorHandler(log *slog.Logger) fiber.ErrorHandler {
	return func(c *fiber.Ctx, err error) error {
		var fe *fiber.Error
		if errors.As(err, &fe) {
			if fe.Code >= 500 {
				log.Error("http hata", "method", c.Method(), "path", c.Path(), "code", fe.Code, "err", fe.Message)
			}
			return c.Status(fe.Code).JSON(errorBody{Error: apiError{Code: httpCodeToErrorCode(fe.Code), Message: fe.Message}})
		}
		log.Error("http hata", "method", c.Method(), "path", c.Path(), "err", err)
		return c.Status(fiber.StatusInternalServerError).JSON(errorBody{
			Error: apiError{Code: protocol.ErrCodeInternal, Message: "beklenmeyen sunucu hatası"},
		})
	}
}

// httpCodeToErrorCode Fiber durum kodlarını protocol hata sözlüğüne eşler.
func httpCodeToErrorCode(status int) string {
	switch status {
	case fiber.StatusBadRequest:
		return protocol.ErrCodeBadRequest
	case fiber.StatusUnauthorized, fiber.StatusForbidden:
		return protocol.ErrCodeUnauthorized
	case fiber.StatusNotFound:
		return protocol.ErrCodeNotFound
	case fiber.StatusConflict:
		return protocol.ErrCodeConflict
	case fiber.StatusRequestEntityTooLarge:
		return protocol.ErrCodeTooLarge
	default:
		if status >= 500 {
			return protocol.ErrCodeInternal
		}
		return protocol.ErrCodeBadRequest
	}
}

// failLLM sağlayıcı hatalarını 502/504'e çevirir.
func failLLM(c *fiber.Ctx, err error) error {
	var pe *llm.ProviderError
	if errors.As(err, &pe) {
		log := "LLM sağlayıcısı hata döndürdü"
		if pe.StatusCode == 0 {
			log = "LLM sağlayıcısına ulaşılamadı"
		}
		return fail(c, fiber.StatusBadGateway, protocol.ErrCodeLLM, log+": "+pe.Message)
	}
	return fail(c, fiber.StatusBadGateway, protocol.ErrCodeLLM, "LLM isteği başarısız")
}
