package inventaire

import (
	"notabug.org/apiote/amuse/datastructure"
	"notabug.org/apiote/amuse/network"

	"database/sql"
	"encoding/json"
	"net/http"
	"strings"

	"notabug.org/apiote/gott"
)

type InventaireUriResponse struct {
	Entities map[string]struct {
		Labels       map[string]string
		Claims       map[string][]string
		Descriptions map[string]string
		Image        struct {
			Url     string
			Credits struct {
				Text string
				Url  string
			}
		}
		Sitelinks map[string]string
	}
}

type InventaireClaimResponse struct {
	Uris []string
}

func createBookRequest(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	result.Client = &http.Client{}
	httpRequest, err := http.NewRequest("GET", "https://inventaire.io/api/entities?action=by-uris&uris="+request.Id+"&refresh=false", nil)
	result.Request = httpRequest
	return gott.Tuple(args), err
}

func findByTag(haystack map[string]string, tag, suffix string) string {
	tag = strings.ToLower(tag)
	result := haystack[tag+suffix]
	if result == "" {
		tag = strings.Split(tag, "-")[0]
		result = haystack[tag+suffix]
	}
	if result == "" {
		tag = "en"
		result = haystack[tag+suffix]
	}
	return result
}

func unmarshalBook(args ...interface{}) (interface{}, error) {
	id := args[0].(*network.Request).Id
	language := args[0].(*network.Request).Language
	result := args[1].(*network.Result)
	response := &InventaireUriResponse{}
	err := json.Unmarshal(result.Body, response)
	book := &datastructure.Book{}
	book.Id = id
	book.Uri = "/books/" + id
	source := datastructure.Source{
		Url:  "https://inventaire.io/entity/" + id,
		Name: "Inventaire",
	}
	book.Source = append(book.Source, source)
	book.Authors = append(book.Authors, response.Entities[id].Claims["wdt:P50"]...)
	book.Title = findByTag(response.Entities[id].Labels, language, "")
	book.Article = findByTag(response.Entities[id].Sitelinks, language, "wiki")
	book.Description = findByTag(response.Entities[id].Descriptions, language, "")
	result.Result = book
	return gott.Tuple(args), err
}

func getBook(args ...interface{}) (interface{}, error) {
	id := args[0].(string)
	language := args[1].(string)
	book := args[2].(*datastructure.Book)
	result, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
		Bind(createBookRequest).
		//Bind(cache.getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		//Tee(cache.cleanCache).
		//Tee(cache.saveCacheEntry).
		Bind(unmarshalBook).
		Finish()

	if err == nil {
		*book = *result.(gott.Tuple)[1].(*network.Result).Result.(*datastructure.Book)
	}
	return gott.Tuple(args), err
}

func createAuthorsRequest(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	result.Client = &http.Client{}
	httpRequest, err := http.NewRequest("GET", "https://inventaire.io/api/entities?action=by-uris&uris="+request.Id+"&refresh=false", nil)
	result.Request = httpRequest
	return gott.Tuple(args), err
}

func unmarshalAuthors(args ...interface{}) (interface{}, error) {
	id := args[0].(*network.Request).Id
	language := args[0].(*network.Request).Language
	result := args[1].(*network.Result)
	response := &InventaireUriResponse{}
	err := json.Unmarshal(result.Body, response)
	ids := strings.Split(id, "|")
	result.Result = []string{}
	for _, authorId := range ids {
		result.Result = append(result.Result.([]string), findByTag(response.Entities[authorId].Labels, language, ""))
	}
	return gott.Tuple(args), err
}

func getAuthors(args ...interface{}) (interface{}, error) {
	language := args[1].(string)
	book := args[2].(*datastructure.Book)
	id := strings.Join(book.Authors, "|")
	author, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
		Bind(createAuthorsRequest).
		//Bind(cache.getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		//Tee(cache.cleanCache).
		//Tee(cache.saveCacheEntry).
		Bind(unmarshalAuthors).
		Finish()

	if err == nil {
		book.Authors = author.(gott.Tuple)[1].(*network.Result).Result.([]string)
	}
	return gott.Tuple(args), err
}

func createEditionRequest(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	result.Client = &http.Client{}
	httpRequest, err := http.NewRequest("GET", "https://inventaire.io/api/entities?action=reverse-claims&value="+request.Id+"&property=wdt:P629&refresh=false", nil)
	result.Request = httpRequest
	return gott.Tuple(args), err
}

func unmarshalEdition(args ...interface{}) (interface{}, error) {
	result := args[1].(*network.Result)
	response := &InventaireClaimResponse{}
	err := json.Unmarshal(result.Body, response)
	if len(response.Uris) > 0 {
		result.Result = response.Uris[0]
	} else {
		result.Result = ""
	}
	return gott.Tuple(args), err
}

func getEditions(args ...interface{}) (interface{}, error) {
	id := args[0].(string)
	language := args[1].(string)
	book := args[2].(*datastructure.Book)
	edition, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
		Bind(createEditionRequest).
		//Bind(cache.getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		//Tee(cache.cleanCache).
		//Tee(cache.saveCacheEntry).
		Bind(unmarshalEdition).
		Finish()

	if err == nil {
		book.Cover = edition.(gott.Tuple)[1].(*network.Result).Result.(string)
	}
	return gott.Tuple(args), err
}

func GetBook(id string, language string, connection *sql.DB) (*datastructure.Book, error) {
	r, err := gott.
		NewResult(gott.Tuple{id, language, &datastructure.Book{}}).
		Bind(getBook).
		Bind(getAuthors).
		Bind(getEditions).
		Finish()

	if err != nil {
		return &datastructure.Book{}, err
	} else {
		return r.(gott.Tuple)[2].(*datastructure.Book), nil
	}
}
