package platform

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"image"
	_ "image/gif"  // register GIF format
	_ "image/jpeg" // register JPEG format
	_ "image/png"  // register PNG format
	"io"
	"math"
	"time"

	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
	"github.com/rwcarlsen/goexif/exif"
	"github.com/rwcarlsen/goexif/tiff"
	"github.com/twpayne/go-geom"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db/gen/nidus-sync/publicreport/model"
	querypublicreport "source.gleipnir.technology/Gleipnir/nidus-sync/db/query/publicreport"
	"source.gleipnir.technology/Gleipnir/nidus-sync/geomutil"
	//exif "github.com/rwcarlsen/goexif/exif"
	//"github.com/dsoprea/go-exif-extra/format"
)

type GPS struct {
	Latitude  float64
	Longitude float64
}

type ExifCollection struct {
	GPS  *GPS
	Tags map[string]string
}

type ImageUpload struct {
	Bounds      image.Rectangle
	ContentType string
	Exif        *ExifCollection
	Format      string

	UploadFilesize int
	UploadFilename string
	UUID           uuid.UUID
}

func (e *ExifCollection) Walk(name exif.FieldName, tag *tiff.Tag) error {
	e.Tags[string(name)] = tag.String()
	return nil
}
func ImageExtractExif(content_type string, file_bytes []byte) (result *ExifCollection, err error) {
	/*
		Using "github.com/evanoberholster/imagemeta"
		meta, err := imagemeta.Decode(bytes.NewReader(file_bytes))
		if err != nil {
			return result, fmt.Errorf("Failed to decode image meta: %w", err)
		}
		result.GPS = &GPS{
			Latitude: meta.GPS.Latitude(),
			Longitude: meta.GPS.Longitude(),
		}
		return result, err
	*/

	e, err := exif.Decode(bytes.NewReader(file_bytes))
	if err != nil {
		if err.Error() == "exif: failed to find exif intro marker" {
			return nil, nil
		} else if errors.Is(err, io.EOF) {
			return nil, nil
		}
		return nil, fmt.Errorf("Failed to decode image meta: %w", err)
	}
	lat, lng, _ := e.LatLong()
	result = &ExifCollection{
		GPS: &GPS{
			Latitude:  lat,
			Longitude: lng,
		},
		Tags: make(map[string]string, 0),
	}
	err = e.Walk(result)
	return result, err
}

func saveImageUploads(ctx context.Context, txn db.Ex, uploads []ImageUpload) ([]model.Image, error) {
	images := make([]model.Image, 0)
	for _, u := range uploads {
		var location *geom.T
		if u.Exif != nil && u.Exif.GPS != nil && !(math.IsNaN(u.Exif.GPS.Longitude) || math.IsNaN(u.Exif.GPS.Latitude)) {
			l := geomutil.PointFromLngLat(u.Exif.GPS.Longitude, u.Exif.GPS.Latitude)
			location = &l
		}
		image := model.Image{
			// ID:
			ContentType:      u.ContentType,
			Created:          time.Now(),
			Location:         location,
			ResolutionX:      int32(u.Bounds.Max.X),
			ResolutionY:      int32(u.Bounds.Max.Y),
			StorageUUID:      u.UUID,
			StorageSize:      int64(u.UploadFilesize),
			UploadedFilename: u.UploadFilename,
		}
		image, err := querypublicreport.ImageInsert(ctx, txn, image)
		if err != nil {
			return images, fmt.Errorf("Failed to create photo records: %w", err)
		}

		// TODO: figure out how to do this via the setter...?
		if u.Exif != nil {
			exif_models := make([]model.ImageExif, len(u.Exif.Tags))
			i := 0
			for k, v := range u.Exif.Tags {
				to_save := trimQuotes(v)
				exif_models[i] = model.ImageExif{
					ImageID: image.ID,
					Name:    k,
					Value:   to_save,
				}
			}
			if len(exif_models) > 0 {
				_, err = querypublicreport.ImageExifInserts(ctx, txn, exif_models)
				if err != nil {
					return images, fmt.Errorf("Failed to create photo exif records: %w", err)
				}
			}
			log.Info().Int32("id", image.ID).Int("tags", len(u.Exif.Tags)).Msg("Saved an uploaded file to the database")
		} else {
			log.Info().Int32("id", image.ID).Int("tags", 0).Msg("Saved an uploaded file without EXIF data")
		}
		images = append(images, image)
	}
	return images, nil
}

// Given a string like "\"foo\"" return "foo".
func trimQuotes(s string) string {
	if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
		return s[1 : len(s)-1]
	}
	return s
}
