package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"

	translate "cloud.google.com/go/translate/apiv3"
	"cloud.google.com/go/translate/apiv3/translatepb"

	"github.com/microcosm-cc/bluemonday"
)

const project_id string = "india-translate-testing-452100"

type translationStruct struct {
	En       string `db:"english" json:"en"`
	Hi       string `db:"hindi" json:"hi"`
	Bn       string `db:"bengali" json:"bn"`
	Mr       string `db:"marathi" json:"mr"`
	Ta       string `db:"tamil" json:"ta"`
	Te       string `db:"telugu" json:"te"`
	Kn       string `db:"kannada" json:"kn"`
	Ml       string `db:"malayalam" json:"ml"`
	Or       string `db:"oriya" json:"or"`
	Gu       string `db:"gujarati" json:"gu"`
	Ur       string `db:"urdu" json:"ur"`
	Lus      string `db:"mizo" json:"lus"`
	As       string `db:"assamese" json:"as"`
	Pa       string `db:"punjabi" json:"pa"`
	Mai      string `db:"maithili" json:"mai"`
	Ne       string `db:"nepali" json:"ne"`
	Gom      string `db:"konkani" json:"gom"`
	Tcy      string `db:"tulu" json:"tcy"`
	Bho      string `db:"bhojpuri" json:"bho"`
	Doi      string `db:"dogri" json:"doi"`
	Mni_mtei string `db:"manipuri" json:"mni-Mtei"`
	Sd       string `db:"sindhi" json:"sd"`
	Awa      string `db:"awadhi" json:"awa"`
}

var lang_codes []string = []string{
	"hi",       // Hindi
	"bn",       // Bengali
	"mr",       // Marathi
	"ta",       // Tamil
	"te",       // Telugu
	"kn",       // Kannada
	"ml",       // Malayalam
	"or",       // Oriya
	"gu",       // Gujarati
	"ur",       // Urdu
	"lus",      // Mizo
	"as",       // Assamese
	"pa",       // Punjabi
	"mai",      // Maithili
	"ne",       // Nepali
	"gom",      // Konkani
	"tcy",      // Tulu
	"bho",      // Bhojpuri
	"doi",      // Dogri
	"mni-Mtei", // Manipuri
	"sd",       // Sindhi
	"awa",      // Awadhi
}

var db *sqlx.DB
var p *bluemonday.Policy

func cleanup() {
	db.Close()
	log.Printf("Shutting down...\n")
	return
}

/*
	Returns the cached translation from the database, for the given english text. The first parameter

indicates whether or not the translation exists.
*/
func getCachedTranslation(data string) (bool, translationStruct) {
	prepared, err := db.Preparex("SELECT * from TRANSLATIONS WHERE english = ? COLLATE NOCASE")
	if err != nil {
		panic(err)
	}

	translations := translationStruct{}
	err = prepared.Get(&translations, data)
	if err != nil {
		if err == sql.ErrNoRows {
			return false, translations
		} else {
			panic(err)
		}
	} else {
		return true, translations
	}
}

func addToDatabase(translation translationStruct) {
	_, err := db.NamedExec(`INSERT INTO translations VALUES (:english, :hindi, :bengali, :marathi, :tamil, :telugu, :kannada, :malayalam, :oriya, :gujarati, :urdu, :mizo, :assamese, :punjabi, :maithili, :nepali, :konkani, :tulu, :bhojpuri, :dogri, :manipuri, :sindhi, :awadhi)`, &translation)
	if err != nil {
		panic(err)
	}
}

func translateText(text string, targetLang string) (result string, err error) {
	return translateTextHelper(project_id, "en-US", targetLang, text)
}

func translateTextHelper(projectID string, sourceLang string, targetLang string, text string) (string, error) {
	// projectID := "my-project-id"
	// sourceLang := "en-US"
	// targetLang := "fr"
	// text := "Text you wish to translate"

	// Instantiates a client
	ctx := context.Background()
	client, err := translate.NewTranslationClient(ctx)
	if err != nil {
		return "", fmt.Errorf("NewTranslationClient: %w", err)
	}
	defer client.Close()

	// Construct request
	req := &translatepb.TranslateTextRequest{
		Parent:             fmt.Sprintf("projects/%s/locations/global", projectID),
		SourceLanguageCode: sourceLang,
		TargetLanguageCode: targetLang,
		MimeType:           "text/plain", // Mime types: "text/plain", "text/html"
		Contents:           []string{text},
	}

	resp, err := client.TranslateText(ctx, req)
	if err != nil {
		return "", fmt.Errorf("TranslateText: %w", err)
	}

	// Display the translation for each input text provided
	return resp.GetTranslations()[0].GetTranslatedText(), nil
}

func handler(w http.ResponseWriter, r *http.Request) {
	queries := r.URL.Query()
	toTranslate := queries["query"][0]
	toTranslate = p.Sanitize(toTranslate)

	if ok, translation := getCachedTranslation(toTranslate); ok {
		translationJson, _ := json.Marshal(translation)
		fmt.Fprintf(w, "%v", string(translationJson))
	} else {
		langToTranslation := make(map[string]string)
		for _, lang_code := range lang_codes {
			translation, err := translateText(toTranslate, lang_code)
			if err != nil {
				panic(err)
			}
			langToTranslation[lang_code] = translation
		}
		langToTranslation["en"] = toTranslate
		langToTranslationJson, _ := json.Marshal(langToTranslation)
		translation := translationStruct{}
		err := json.Unmarshal(langToTranslationJson, &translation)
		addToDatabase(translation)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(w, "%v", string(langToTranslationJson))
	}
}

func main() {
	log.Printf("Starting server...")
	var err error
	db, err = sqlx.Connect("sqlite3", "../translations.db")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// HTML sanitization
	p = bluemonday.UGCPolicy()

	// Catch signal
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs)
	go func() {
		for sig := range sigs {
			log.Printf("Received signal: %s", sig)
			switch sig {
			case syscall.SIGURG:
				log.Printf("Ignoring sigurg")
			case syscall.SIGTERM, syscall.SIGINT:
				cleanup()
				os.Exit(1)
			}
		}
	}()

	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":9090", nil))
}
