package api

import (
	"context"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/lib/pq"

	"github.com/rycroftapparel/workpulse-api/internal/httpapi"
)

const (
	maxChatAttachmentBytes = 5 << 20 // 5 MB
	maxChatAttachments     = 6
)

type chatAttachmentJSON struct {
	ID       string `json:"id"`
	URL      string `json:"url"`
	Name     string `json:"name"`
	MimeType string `json:"mimeType"`
	Size     int64  `json:"size"`
}

func (s *Server) chatAttachmentsDir(messageID uint64) string {
	return filepath.Join(s.Cfg.UploadDir, "chat", fmt.Sprintf("%d", messageID))
}

func (s *Server) chatAttachmentPublicPath(messageID uint64, filename string) string {
	return fmt.Sprintf("/api/v1/uploads/chat/%d/%s", messageID, filename)
}

func (s *Server) chatAttachmentsByMessageIDs(ctx context.Context, ids []uint64) (map[uint64][]chatAttachmentJSON, error) {
	out := make(map[uint64][]chatAttachmentJSON, len(ids))
	if len(ids) == 0 {
		return out, nil
	}
	rows, err := s.DB.QueryContext(ctx, `
		SELECT message_id, id, url, name, mime_type, size
		FROM chat_attachments
		WHERE message_id = ANY($1)
		ORDER BY created_at ASC, id ASC`,
		pqArrayUint64(ids),
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var messageID uint64
		var att chatAttachmentJSON
		if err := rows.Scan(&messageID, &att.ID, &att.URL, &att.Name, &att.MimeType, &att.Size); err != nil {
			return nil, err
		}
		out[messageID] = append(out[messageID], att)
	}
	return out, rows.Err()
}

func pqArrayUint64(ids []uint64) interface{} {
	return pq.Array(uint64SliceToInt64(ids))
}

func (s *Server) saveChatAttachments(c *gin.Context, messageID uint64, files []*multipart.FileHeader) ([]chatAttachmentJSON, bool) {
	if len(files) == 0 {
		return []chatAttachmentJSON{}, true
	}
	if len(files) > maxChatAttachments {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", fmt.Sprintf("Maksimal %d gambar per pesan.", maxChatAttachments)))
		return nil, false
	}

	dir := s.chatAttachmentsDir(messageID)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("storage", "Gagal menyiapkan penyimpanan gambar."))
		return nil, false
	}

	ctx, cancel := s.ctx(c)
	defer cancel()
	out := make([]chatAttachmentJSON, 0, len(files))
	var savedPaths []string

	for _, file := range files {
		if file == nil {
			continue
		}
		if file.Size <= 0 || file.Size > maxChatAttachmentBytes {
			removeSavedFiles(savedPaths)
			c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "Ukuran gambar maksimal 5 MB."))
			return nil, false
		}

		src, err := file.Open()
		if err != nil {
			removeSavedFiles(savedPaths)
			c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "File tidak dapat dibaca."))
			return nil, false
		}

		head := make([]byte, 512)
		n, _ := io.ReadFull(src, head)
		head = head[:n]
		ext, okExt := sniffImageExt(head, file.Header.Get("Content-Type"))
		if !okExt {
			_ = src.Close()
			removeSavedFiles(savedPaths)
			c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "Format gambar harus JPG, PNG, WebP, atau GIF."))
			return nil, false
		}

		attID := newReportAttachmentID()
		filename := attID + ext
		destPath := filepath.Join(dir, filename)
		dst, err := os.Create(destPath)
		if err != nil {
			_ = src.Close()
			removeSavedFiles(savedPaths)
			c.JSON(http.StatusInternalServerError, httpapi.Fail("storage", "Gagal menyimpan gambar."))
			return nil, false
		}
		if _, err := dst.Write(head); err != nil {
			_ = src.Close()
			_ = dst.Close()
			removeSavedFiles(append(savedPaths, destPath))
			c.JSON(http.StatusInternalServerError, httpapi.Fail("storage", "Gagal menyimpan gambar."))
			return nil, false
		}
		if _, err := io.Copy(dst, src); err != nil {
			_ = src.Close()
			_ = dst.Close()
			removeSavedFiles(append(savedPaths, destPath))
			c.JSON(http.StatusInternalServerError, httpapi.Fail("storage", "Gagal menyimpan gambar."))
			return nil, false
		}
		_ = src.Close()
		_ = dst.Close()
		savedPaths = append(savedPaths, destPath)

		name := strings.TrimSpace(file.Filename)
		if name == "" {
			name = "chat-image" + ext
		}
		att := chatAttachmentJSON{
			ID:       attID,
			URL:      s.chatAttachmentPublicPath(messageID, filename),
			Name:     name,
			MimeType: mimeFromExt(ext),
			Size:     file.Size,
		}
		if _, err := s.DB.ExecContext(ctx,
			`INSERT INTO chat_attachments (id, message_id, url, name, mime_type, size) VALUES ($1, $2, $3, $4, $5, $6)`,
			att.ID, messageID, att.URL, att.Name, att.MimeType, att.Size,
		); err != nil {
			removeSavedFiles(savedPaths)
			c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
			return nil, false
		}
		out = append(out, att)
	}

	return out, true
}

func removeSavedFiles(paths []string) {
	for _, path := range paths {
		_ = os.Remove(path)
	}
}
