package server

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/golang/protobuf/proto"
	"go.uber.org/zap"

	"github.com/status-im/status-go/images"
	"github.com/status-im/status-go/protocol/common"
	"github.com/status-im/status-go/protocol/protobuf"
)

func getThumbnailPayload(db *sql.DB, logger *zap.Logger, msgID string, thumbnailURL string) ([]byte, error) {
	var payload []byte

	var result []byte
	err := db.QueryRow(`SELECT unfurled_links FROM user_messages WHERE id = ?`, msgID).Scan(&result)
	if err != nil {
		return payload, fmt.Errorf("could not find message with message-id '%s': %w", msgID, err)
	}

	var links []*protobuf.UnfurledLink
	err = json.Unmarshal(result, &links)
	if err != nil {
		return payload, fmt.Errorf("failed to unmarshal protobuf.UrlPreview: %w", err)
	}

	for _, p := range links {
		if p.Url == thumbnailURL {
			payload = p.ThumbnailPayload
			break
		}
	}

	return payload, nil
}

func handleLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		params := r.URL.Query()
		parsed := ParseImageParams(logger, params)

		if parsed.MessageID == "" {
			http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest)
			return
		}

		if parsed.URL == "" {
			http.Error(w, "missing query parameter 'url'", http.StatusBadRequest)
			return
		}

		thumbnail, err := getThumbnailPayload(db, logger, parsed.MessageID, parsed.URL)
		if err != nil {
			logger.Error("failed to get thumbnail", zap.String("msgID", parsed.MessageID))
			http.Error(w, "failed to get thumbnail", http.StatusInternalServerError)
			return
		}

		mimeType, err := images.GetMimeType(thumbnail)
		if err != nil {
			http.Error(w, "mime type not supported", http.StatusNotImplemented)
			return
		}

		w.Header().Set("Content-Type", "image/"+mimeType)
		w.Header().Set("Cache-Control", "no-store")

		_, err = w.Write(thumbnail)
		if err != nil {
			logger.Error("failed to write response", zap.Error(err))
		}
	}
}

func getStatusLinkPreviewImage(p *protobuf.UnfurledStatusLink, imageID common.MediaServerImageID) ([]byte, error) {

	switch imageID {
	case common.MediaServerContactIcon:
		contact := p.GetContact()
		if contact == nil {
			return nil, fmt.Errorf("this is not a contact link")
		}
		if contact.Icon == nil {
			return nil, fmt.Errorf("contact icon is empty")
		}
		return contact.Icon.Payload, nil

	case common.MediaServerCommunityIcon:
		community := p.GetCommunity()
		if community == nil {
			return nil, fmt.Errorf("this is not a community link")
		}
		if community.Icon == nil {
			return nil, fmt.Errorf("community icon is empty")
		}
		return community.Icon.Payload, nil

	case common.MediaServerCommunityBanner:
		community := p.GetCommunity()
		if community == nil {
			return nil, fmt.Errorf("this is not a community link")
		}
		if community.Banner == nil {
			return nil, fmt.Errorf("community banner is empty")
		}
		return community.Banner.Payload, nil

	case common.MediaServerChannelCommunityIcon:
		channel := p.GetChannel()
		if channel == nil {
			return nil, fmt.Errorf("this is not a community channel link")
		}
		if channel.Community == nil {
			return nil, fmt.Errorf("channel community is empty")
		}
		if channel.Community.Icon == nil {
			return nil, fmt.Errorf("channel community icon is empty")
		}
		return channel.Community.Icon.Payload, nil

	case common.MediaServerChannelCommunityBanner:
		channel := p.GetChannel()
		if channel == nil {
			return nil, fmt.Errorf("this is not a community channel link")
		}
		if channel.Community == nil {
			return nil, fmt.Errorf("channel community is empty")
		}
		if channel.Community.Banner == nil {
			return nil, fmt.Errorf("channel community banner is empty")
		}
		return channel.Community.Banner.Payload, nil
	}

	return nil, fmt.Errorf("value not supported")
}

func getStatusLinkPreviewThumbnail(db *sql.DB, messageID string, URL string, imageID common.MediaServerImageID) ([]byte, int, error) {
	var messageLinks []byte
	err := db.QueryRow(`SELECT unfurled_status_links FROM user_messages WHERE id = ?`, messageID).Scan(&messageLinks)
	if err != nil {
		return nil, http.StatusBadRequest, fmt.Errorf("could not find message with message-id '%s': %w", messageID, err)
	}

	var links protobuf.UnfurledStatusLinks
	err = proto.Unmarshal(messageLinks, &links)
	if err != nil {
		return nil, http.StatusInternalServerError, fmt.Errorf("failed to unmarshal protobuf.UrlPreview: %w", err)
	}

	for _, p := range links.UnfurledStatusLinks {
		if p.Url == URL {
			thumbnailPayload, err := getStatusLinkPreviewImage(p, imageID)
			if err != nil {
				return nil, http.StatusBadRequest, fmt.Errorf("invalid query parameter 'image-id' value: %w", err)
			}
			return thumbnailPayload, http.StatusOK, nil
		}
	}

	return nil, http.StatusBadRequest, fmt.Errorf("no link preview found for given url")
}

func handleStatusLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		params := r.URL.Query()
		parsed := ParseImageParams(logger, params)

		if parsed.MessageID == "" {
			http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest)
			return
		}

		if parsed.URL == "" {
			http.Error(w, "missing query parameter 'url'", http.StatusBadRequest)
			return
		}

		if parsed.ImageID == "" {
			http.Error(w, "missing query parameter 'image-id'", http.StatusBadRequest)
			return
		}

		thumbnail, httpsStatusCode, err := getStatusLinkPreviewThumbnail(db, parsed.MessageID, parsed.URL, common.MediaServerImageID(parsed.ImageID))
		if err != nil {
			http.Error(w, err.Error(), httpsStatusCode)
			return
		}

		mimeType, err := images.GetMimeType(thumbnail)
		if err != nil {
			http.Error(w, "mime type not supported", http.StatusNotImplemented)
			return
		}

		w.Header().Set("Content-Type", "image/"+mimeType)
		w.Header().Set("Cache-Control", "no-store")

		_, err = w.Write(thumbnail)
		if err != nil {
			logger.Error("failed to write response", zap.Error(err))
		}
	}
}