package server

import (
	"bytes"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"net/http"
	"strings"

	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"github.com/kataras/iris/v12"
	"github.com/rs/zerolog/log"
	"golang.org/x/image/font"
	"golang.org/x/image/font/gofont/gomonobold"

	"pluja.dev/kycnot.me/database"
)

func (s *Server) handleVerifyPow(c iris.Context) {
	// Get id, nonce and proof from the request
	id := c.Params().Get("id")
	nonce := c.Params().Get("nonce")

	// Verify the proof of work
	valid := s.PowChallenger.PowVerifyProof(id, nonce)

	// Return the result
	c.JSON(iris.Map{
		"valid":  valid,
		"status": iris.StatusOK,
	})
}

// Proxies the request to the logo URL of a service by its ID
func (s *Server) handleApiPicture(c iris.Context) {
	id := c.Params().Get("id")

	service, err := database.Pb.GetServiceById(id)
	if err != nil {
		log.Error().Err(err).Msg("Could not get service")
		respondWithPlaceholder(c, "?")
		return
	}

	if service.LogoURL == "" {
		log.Debug().Msgf("Image %s not found in cache", service.ID)
		respondWithPlaceholder(c, service.Name)
		return
	}

	if imageData, err := s.Cache.Get(fmt.Sprintf("img-%s", service.ID)); err == nil {
		log.Debug().Msgf("Found image %s in cache", service.ID)
		ctt, _ := s.Cache.Get(fmt.Sprintf("ctt-%s", service.ID))
		c.ContentType(string(ctt))
		c.StatusCode(iris.StatusOK)
		c.Write(imageData)
		return
	}

	log.Debug().Msgf("Image %s not found in cache", service.ID)
	client := &http.Client{}
	req, err := http.NewRequest("GET", service.LogoURL, nil)
	if err != nil {
		log.Error().Err(err).Msg("Failed to create HTTP request")
		return
	}

	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.3")
	req.Header.Set("Referer", service.Urls[0])
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != http.StatusOK {
		log.Error().Err(err).Msg("Could not get image")
		respondWithPlaceholder(c, service.Name)
		return
	}
	defer resp.Body.Close()

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error().Err(err).Msg("Could not read response body")
		respondWithPlaceholder(c, service.Name)
		return
	}

	if bodyBytes == nil {
		log.Error().Msg("Could not read response body")
		respondWithPlaceholder(c, service.Name)
		return
	}

	s.Cache.Set(fmt.Sprintf("img-%s", service.ID), bodyBytes)
	s.Cache.Set(fmt.Sprintf("ctt-%s", service.ID), []byte(resp.Header.Get("Content-Type")))

	c.ContentType(resp.Header.Get("Content-Type"))
	c.StatusCode(iris.StatusOK)
	c.Write(bodyBytes)
}

func respondWithPlaceholder(c iris.Context, serviceName string) {
	log.Debug().Msgf("Generating placeholder for %s", serviceName)
	const width, height = 250, 250
	const margin = 15
	firstLetter := strings.ToUpper(string(serviceName[0]))

	// Create an image
	img := image.NewRGBA(image.Rect(0, 0, width, height))

	// Set background to black
	draw.Draw(img, img.Bounds(), image.NewUniform(color.Black), image.Point{}, draw.Src)

	// Load the monospace font
	f, err := truetype.Parse(gomonobold.TTF)
	if err != nil {
		log.Error().Err(err).Msg("could not parse font")
		return
	}

	// Initialize freetype context
	ctx := freetype.NewContext()
	ctx.SetDPI(72)
	ctx.SetFont(f)

	// Parse hex color and set text color
	var r, g, b uint8
	hexColor := "#84cc16"
	_, err = fmt.Sscanf(hexColor, "#%02x%02x%02x", &r, &g, &b)
	if err != nil {
		log.Error().Err(err).Msg("Invalid hex color")
		return
	}
	textColor := color.RGBA{R: r, G: g, B: b, A: 255}
	ctx.SetSrc(image.NewUniform(textColor))

	ctx.SetClip(img.Bounds())
	ctx.SetDst(img)

	// Dynamically calculate the maximum font size
	var fontSize float64
	var textWidth, textHeight int
	for fontSize = 205; fontSize > 10; fontSize -= 0.5 {
		ctx.SetFontSize(fontSize)
		opts := truetype.Options{Size: fontSize}
		face := truetype.NewFace(f, &opts)
		textWidth = font.MeasureString(face, firstLetter).Round()
		ascent, descent := face.Metrics().Ascent, face.Metrics().Descent
		textHeight = (ascent + descent).Ceil()

		if textWidth <= width-margin*2 && textHeight <= height-margin*2 {
			break
		}
	}

	// Calculate position for the text
	x := (width - textWidth) / 2
	y := (height-textHeight)/2 + textHeight - int(ctx.PointToFixed(fontSize*0.2)>>6)
	pt := freetype.Pt(x, y)

	// Draw the text
	if _, err := ctx.DrawString(firstLetter, pt); err != nil {
		log.Error().Err(err).Msg("Failed to draw string")
		return
	}

	// Encode to PNG
	var buf bytes.Buffer
	if err := png.Encode(&buf, img); err != nil {
		log.Error().Err(err).Msg("Failed to encode image")
		return
	}

	c.ContentType("image/png")
	c.StatusCode(iris.StatusOK)
	c.Write(buf.Bytes())
}
