package api

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

	"gitlab.global.bsf.tools/esv/bsf/bsf-integration/orchard/orchard-mvp/internal/models"
	"gitlab.global.bsf.tools/esv/bsf/bsf-integration/orchard/orchard-mvp/internal/storage"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

type Handler struct {
	db      *storage.Database
	s3      *storage.S3Storage
	logger  *zap.Logger
}

func NewHandler(db *storage.Database, s3 *storage.S3Storage, logger *zap.Logger) *Handler {
	return &Handler{
		db:     db,
		s3:     s3,
		logger: logger,
	}
}

// Health check endpoint
func (h *Handler) Health(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"status": "healthy"})
}

// Grove handlers

func (h *Handler) CreateGrove(c *gin.Context) {
	var grove models.Grove
	if err := c.ShouldBindJSON(&grove); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	grove.CreatedBy = c.GetString("user_id")
	if err := h.db.CreateGrove(c.Request.Context(), &grove); err != nil {
		h.logger.Error("failed to create grove", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create grove"})
		return
	}

	h.logAudit(c, "create_grove", grove.Name)
	c.JSON(http.StatusCreated, grove)
}

func (h *Handler) GetGrove(c *gin.Context) {
	groveName := c.Param("grove")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil {
		h.logger.Error("failed to get grove", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get grove"})
		return
	}

	if grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	c.JSON(http.StatusOK, grove)
}

func (h *Handler) ListGroves(c *gin.Context) {
	userID := c.GetString("user_id")

	groves, err := h.db.ListGroves(c.Request.Context(), userID)
	if err != nil {
		h.logger.Error("failed to list groves", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list groves"})
		return
	}

	c.JSON(http.StatusOK, groves)
}

// Tree handlers

func (h *Handler) CreateTree(c *gin.Context) {
	groveName := c.Param("grove")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	var tree models.Tree
	if err := c.ShouldBindJSON(&tree); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	tree.GroveID = grove.ID
	if err := h.db.CreateTree(c.Request.Context(), &tree); err != nil {
		h.logger.Error("failed to create tree", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create tree"})
		return
	}

	h.logAudit(c, "create_tree", fmt.Sprintf("%s/%s", groveName, tree.Name))
	c.JSON(http.StatusCreated, tree)
}

func (h *Handler) ListTrees(c *gin.Context) {
	groveName := c.Param("grove")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	trees, err := h.db.ListTrees(c.Request.Context(), grove.ID)
	if err != nil {
		h.logger.Error("failed to list trees", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list trees"})
		return
	}

	c.JSON(http.StatusOK, trees)
}

// Fruit handlers (content-addressable storage)

func (h *Handler) Cultivate(c *gin.Context) {
	groveName := c.Param("grove")
	treeName := c.Param("tree")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	tree, err := h.db.GetTree(c.Request.Context(), grove.ID, treeName)
	if err != nil || tree == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "tree not found"})
		return
	}

	// Get the uploaded file
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
		return
	}
	defer file.Close()

	// Store in S3 (content-addressable)
	hash, size, err := h.s3.Store(c.Request.Context(), file)
	if err != nil {
		h.logger.Error("failed to store artifact", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store artifact"})
		return
	}

	// Create fruit record
	fruit := &models.Fruit{
		ID:           hash,
		Size:         size,
		ContentType:  header.Header.Get("Content-Type"),
		OriginalName: header.Filename,
		CreatedBy:    c.GetString("user_id"),
		S3Key:        fmt.Sprintf("%s/%s/%s", hash[:2], hash[2:4], hash),
	}

	if err := h.db.CreateFruit(c.Request.Context(), fruit); err != nil {
		h.logger.Error("failed to create fruit record", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create fruit record"})
		return
	}

	// Create harvest record
	harvest := &models.Harvest{
		FruitID:      hash,
		TreeID:       tree.ID,
		OriginalName: header.Filename,
		HarvestedBy:  c.GetString("user_id"),
		SourceIP:     c.ClientIP(),
	}

	if err := h.db.CreateHarvest(c.Request.Context(), harvest); err != nil {
		h.logger.Error("failed to create harvest record", zap.Error(err))
	}

	// Optionally create a graft (tag) if specified
	tag := c.PostForm("tag")
	if tag != "" {
		graft := &models.Graft{
			TreeID:    tree.ID,
			Name:      tag,
			FruitID:   hash,
			CreatedBy: c.GetString("user_id"),
		}
		if err := h.db.CreateGraft(c.Request.Context(), graft); err != nil {
			h.logger.Error("failed to create graft", zap.Error(err))
		}
	}

	h.logAudit(c, "cultivate", fmt.Sprintf("%s/%s/%s", groveName, treeName, hash))

	c.JSON(http.StatusCreated, gin.H{
		"fruit_id": hash,
		"size":     size,
		"grove":    groveName,
		"tree":     treeName,
		"tag":      tag,
	})
}

func (h *Handler) Harvest(c *gin.Context) {
	groveName := c.Param("grove")
	treeName := c.Param("tree")
	ref := c.Param("ref")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	tree, err := h.db.GetTree(c.Request.Context(), grove.ID, treeName)
	if err != nil || tree == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "tree not found"})
		return
	}

	// Resolve reference to fruit ID
	var fruitID string
	if strings.HasPrefix(ref, "fruit:") {
		// Direct fruit reference
		fruitID = strings.TrimPrefix(ref, "fruit:")
	} else if strings.HasPrefix(ref, "version:") || strings.HasPrefix(ref, "tag:") {
		// Tag/version reference
		tagName := strings.TrimPrefix(ref, "version:")
		tagName = strings.TrimPrefix(tagName, "tag:")
		graft, err := h.db.GetGraft(c.Request.Context(), tree.ID, tagName)
		if err != nil || graft == nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "reference not found"})
			return
		}
		fruitID = graft.FruitID
	} else {
		// Assume it's a tag name
		graft, err := h.db.GetGraft(c.Request.Context(), tree.ID, ref)
		if err != nil || graft == nil {
			// Maybe it's a direct fruit ID
			fruit, err := h.db.GetFruit(c.Request.Context(), ref)
			if err != nil || fruit == nil {
				c.JSON(http.StatusNotFound, gin.H{"error": "reference not found"})
				return
			}
			fruitID = ref
		} else {
			fruitID = graft.FruitID
		}
	}

	// Get fruit metadata
	fruit, err := h.db.GetFruit(c.Request.Context(), fruitID)
	if err != nil || fruit == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "fruit not found"})
		return
	}

	// Track consumer if project URL is provided
	projectURL := c.GetHeader("X-Orchard-Project")
	if projectURL != "" {
		h.db.TrackConsumer(c.Request.Context(), tree.ID, projectURL)
	}

	// Stream content from S3
	reader, err := h.s3.Retrieve(c.Request.Context(), fruitID)
	if err != nil {
		h.logger.Error("failed to retrieve artifact", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve artifact"})
		return
	}
	defer reader.Close()

	h.logAudit(c, "harvest", fmt.Sprintf("%s/%s/%s", groveName, treeName, fruitID))

	c.Header("Content-Type", fruit.ContentType)
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fruit.OriginalName))
	c.Header("X-Orchard-Fruit-ID", fruitID)
	c.Header("X-Orchard-Size", fmt.Sprintf("%d", fruit.Size))

	io.Copy(c.Writer, reader)
}

func (h *Handler) GetFruit(c *gin.Context) {
	fruitID := c.Param("fruit")

	fruit, err := h.db.GetFruit(c.Request.Context(), fruitID)
	if err != nil {
		h.logger.Error("failed to get fruit", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get fruit"})
		return
	}

	if fruit == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "fruit not found"})
		return
	}

	c.JSON(http.StatusOK, fruit)
}

// Graft handlers (aliasing/tagging)

func (h *Handler) CreateGraft(c *gin.Context) {
	groveName := c.Param("grove")
	treeName := c.Param("tree")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	tree, err := h.db.GetTree(c.Request.Context(), grove.ID, treeName)
	if err != nil || tree == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "tree not found"})
		return
	}

	var graft models.Graft
	if err := c.ShouldBindJSON(&graft); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	graft.TreeID = tree.ID
	graft.CreatedBy = c.GetString("user_id")

	// Verify fruit exists
	fruit, err := h.db.GetFruit(c.Request.Context(), graft.FruitID)
	if err != nil || fruit == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "fruit not found"})
		return
	}

	if err := h.db.CreateGraft(c.Request.Context(), &graft); err != nil {
		h.logger.Error("failed to create graft", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create graft"})
		return
	}

	h.logAudit(c, "create_graft", fmt.Sprintf("%s/%s/%s", groveName, treeName, graft.Name))
	c.JSON(http.StatusCreated, graft)
}

func (h *Handler) ListGrafts(c *gin.Context) {
	groveName := c.Param("grove")
	treeName := c.Param("tree")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	tree, err := h.db.GetTree(c.Request.Context(), grove.ID, treeName)
	if err != nil || tree == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "tree not found"})
		return
	}

	grafts, err := h.db.ListGrafts(c.Request.Context(), tree.ID)
	if err != nil {
		h.logger.Error("failed to list grafts", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list grafts"})
		return
	}

	c.JSON(http.StatusOK, grafts)
}

// Consumer tracking handlers

func (h *Handler) GetConsumers(c *gin.Context) {
	groveName := c.Param("grove")
	treeName := c.Param("tree")

	grove, err := h.db.GetGrove(c.Request.Context(), groveName)
	if err != nil || grove == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "grove not found"})
		return
	}

	tree, err := h.db.GetTree(c.Request.Context(), grove.ID, treeName)
	if err != nil || tree == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "tree not found"})
		return
	}

	consumers, err := h.db.GetConsumers(c.Request.Context(), tree.ID)
	if err != nil {
		h.logger.Error("failed to get consumers", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get consumers"})
		return
	}

	c.JSON(http.StatusOK, consumers)
}

// Search handler

func (h *Handler) Search(c *gin.Context) {
	// TODO: Implement format-aware search
	c.JSON(http.StatusOK, gin.H{"message": "search not yet implemented"})
}

// Helper to log audit events
func (h *Handler) logAudit(c *gin.Context, action, resource string) {
	details, _ := json.Marshal(map[string]string{
		"method": c.Request.Method,
		"path":   c.Request.URL.Path,
	})

	log := &models.AuditLog{
		Action:   action,
		Resource: resource,
		UserID:   c.GetString("user_id"),
		Details:  string(details),
		SourceIP: c.ClientIP(),
	}

	if err := h.db.CreateAuditLog(c.Request.Context(), log); err != nil {
		h.logger.Error("failed to create audit log", zap.Error(err))
	}
}
