package api

import (
	"encoding/gob"
	"fmt"
	"log"
	"net/http"
	"time"

	"git.sp4ke.com/sp4ke/bit4sat/db"
	"git.sp4ke.com/sp4ke/bit4sat/ln"
	"git.sp4ke.com/sp4ke/bit4sat/storage"
	"git.sp4ke.com/sp4ke/bit4sat/utils"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/mediocregopher/radix/v3"
)

func download(c *gin.Context) {
	var ok bool

	dlId := c.Param("dlId")

	// Get upload id for dl id
	upId, err := storage.GetUploadIdForDlId(dlId)
	if err != nil {
		utils.JSONErr(c, http.StatusNotFound, "this file does not exist anymore")
		return
	}

	// Get files metadata for upload
	var upFilesMeta []storage.FileUpload
	upFilesMeta, err = storage.GetUploadFilesMeta(upId)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	// Get the download session
	session, err := SessionStore.Get(c.Request, DlSessionKey)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	dlSessVal := session.Values["session-id"]
	var dlSess string

	if dlSess, ok = dlSessVal.(string); dlSessVal != nil && !ok {
		log.Printf("error parsing session dlSessVal")
		utils.JSONErr(c, http.StatusExpectationFailed,
			"start a new download session session")
		return
	}

	dlIdVal := session.Values["dlId"]

	var sessDlId string

	// If no dlId was stored before than this is a new download
	if dlIdVal == nil {
		sessDlId = dlId
	} else {

		if sessDlId, ok = dlIdVal.(string); !ok {
			log.Printf("cannot cast sessDlId value")
			utils.JSONErr(c, http.StatusExpectationFailed,
				"start a new download session session")
			return
		}

	}

	// Test if we are alread in a download session
	// If the stored dlId is different than the
	// current dl id it means we're downloading a different
	// file
	if dlSessVal == nil || (sessDlId != dlId) {
		// This is a new download session
		log.Println("new download session")

		session.Values["dlId"] = dlId

		// Create a unique session id for this download session
		sessId, err := storage.GetShortId()
		if err != nil {
			utils.JSONErrPriv(c, http.StatusInternalServerError, err)
			return
		}

		log.Printf("going to generate invoice for %s", upId)

		//TODO: use fee asked by uploader
		//
		// Get upload data asked by uploader
		up, err := storage.GetUploadById(upId)
		if err != nil {
			utils.JSONErrPriv(c, http.StatusInternalServerError, err)
			return
		}

		invoiceOpts := ln.InvoiceOpts{
			Amount: up.AskAmount,
			Curr:   ln.CurrencyID[up.AskCurrency],
			Memo:   fmt.Sprintf("bit4sat download: %s", sessId),
		}

		log.Printf("invoice opts %#v", invoiceOpts)

		invoice, err := ln.NewInvoice(invoiceOpts)
		if err != nil {
			utils.JSONErrPriv(c, http.StatusInternalServerError, err)
			return
		}

		// Store invoice_id ---> upload_id
		key := fmt.Sprintf("invoice_id_%s_upload_id", invoice.RHash)
		err = db.DB.Redis.Do(radix.FlatCmd(nil, "SET", key, upId))
		if err != nil {
			utils.JSONErrPriv(c, http.StatusInternalServerError, err)
			return
		}

		// Store invoice_<invoice_id>
		invoiceKey := fmt.Sprintf("invoice_%s", invoice.RHash)
		err = db.DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceKey, invoice))

		session.Values["session-id"] = sessId
		session.Values["invoice-id"] = invoice.RHash

		invoiceExpires := time.Time(invoice.ExpiresAt).Sub(time.Now()).Seconds()

		//TODO: this session can be reused during this hour
		session.Options.MaxAge = int(invoiceExpires)

		//log.Printf("session: %#v", session.Values)

		err = session.Save(c.Request, c.Writer)
		if err != nil {
			utils.JSONErrPriv(c, http.StatusInternalServerError, err)
			return
		}

		c.JSON(http.StatusPaymentRequired, gin.H{
			"invoice": invoice,
			"files":   upFilesMeta,
		})

		// This is a returning download session to pay the invoice
	} else {
		log.Printf("continue download session id: %s", dlSess)

		// Get invoice-id linked to sessionId
		invIdVal := session.Values["invoice-id"]
		if invIdVal == nil {
			session.Options.MaxAge = -1

			err = session.Save(c.Request, c.Writer)
			if err != nil {
				utils.JSONErrPriv(c, http.StatusInternalServerError, err)
				return
			}

			utils.JSONErr(c, http.StatusExpectationFailed,
				"invoice not found, start a new download session session")
			return
		}

		var invoiceId string
		if invoiceId, ok = invIdVal.(string); !ok {
			log.Printf("Could not find invoice in session %s", dlSess)
			session.Options.MaxAge = -1

			err = session.Save(c.Request, c.Writer)
			if err != nil {
				utils.JSONErrPriv(c, http.StatusInternalServerError, err)
				return
			}

			utils.JSONErr(c, http.StatusExpectationFailed,
				"invoice not found, start a new download session session")
			return
		}

		// Get invoice from invoice_id
		invoiceKey := fmt.Sprintf("invoice_%s", invoiceId)
		invoice := ln.Invoice{}
		err = db.DB.Redis.Do(radix.FlatCmd(&invoice, "GET", invoiceKey))
		if err != nil {
			utils.JSONErrPriv(c, http.StatusInternalServerError, err)
			return
		}

		// If invoice paid create an authorized download link linked to session
		// The link will expire after after one hour
		// Also bump up the expirey of the DlSession to one hour
		if invoice.Settled {
			log.Println("dl invoice paid, sending")

			downKey, err := storage.GetShortId()
			if err != nil {
				utils.JSONErrPriv(c, http.StatusInternalServerError, err)
				return
			}
			key := fmt.Sprintf("session_%s_download_key_%s", dlSess, downKey)
			// We store the upload id in the value to use it when retreiving
			// files
			db.SetKeyVal(key, upId)
			db.ExpireKey(key, MaxAgeDlKeySession)

			// Session will also expire in 1 hour
			session.Options.MaxAge = MaxAgeDlKeySession
			err = session.Save(c.Request, c.Writer)
			if err != nil {
				utils.JSONErrPriv(c, http.StatusInternalServerError, err)
				return
			}

			c.JSON(http.StatusOK, gin.H{
				"download_key": downKey,
				"invoice":      invoice,
			})
			return
		}

		c.JSON(http.StatusPaymentRequired, gin.H{
			"invoice": invoice,
			"files":   upFilesMeta,
		})
	}

	return
}

func getFiles(c *gin.Context) {
	downKey := c.Param("dlKey")

	// Get the session
	session, err := SessionStore.Get(c.Request, DlSessionKey)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	dlSessVal := session.Values["session-id"]

	var dlSess string
	var ok bool

	if dlSess, ok = dlSessVal.(string); dlSessVal != nil && !ok {
		log.Printf("error parsing session dlSessVal")
		utils.JSONErr(c, http.StatusExpectationFailed,
			"user session lost, start a new download session session")
		return
	}

	if dlSessVal == nil {
		utils.JSONErr(c, http.StatusGone, "the download session has expired")
		return
	}

	key := fmt.Sprintf("session_%s_download_key_%s", dlSess, downKey)

	// Check if download key exists in db
	exists, err := db.Exists(key)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	if !exists {
		utils.JSONErr(c, http.StatusGone, "the download session has expired")
		return
	}

	// We should now allow the user to get the files

	// We retreive the files from the upId stored on the session download key
	var upId string
	err = db.GetFromKey(key, &upId)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	// Get files metadata for upload
	var upFilesMeta []storage.FileUpload
	upFilesMeta, err = storage.GetUploadFilesMeta(upId)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	c.Header("Content-Type", "application/zip")
	c.Header("Content-Disposition", "attachment; filename=download.zip")

	err = storage.ZipFiles(upFilesMeta, c.Writer)
	if err != nil {
		utils.JSONErrPriv(c, http.StatusInternalServerError, err)
		return
	}

	return
}

func TestDownHandler(c *gin.Context) {
	sess := sessions.Default(c)

	test := sess.Get("test")
	log.Printf("%#v", test)
	if test != nil {
		sess.Clear()
		sess.Save()
		c.String(http.StatusOK, "I remember you")
	} else {
		sess.Set("test", 1)
		sess.Save()
		c.String(http.StatusOK, "i dont remember you")
	}

	return
}

func init() {
	gob.Register(&ln.Invoice{})
	gob.Register(&[]storage.FileUpload{})
}
