package wikidata

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

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

	"notabug.org/apiote/gott"
)

type Editions struct {
	Entities map[string]struct {
		OriginalLang string
		Image        struct {
			Url string
		}
	}
}

func queryBookByTmdb(args ...interface{}) (interface{}, error) {
	id := args[0].(*network.Request).Id
	tag := args[0].(*network.Request).Language[:2]
	result := args[1].(*Result)
	repo := result.Repo
	res, err := repo.Query(`SELECT ?book ?bookLabel ?bookseries ?bookseriesLabel WHERE {
  OPTIONAL {
    ?serie wdt:P4983 "` + id + `";
      wdt:P144 ?book.
    ?book wdt:P31 wd:Q47461344.
  }
  OPTIONAL {
    ?film wdt:P4947 "` + id + `";
      wdt:P144 ?book.
    ?book wdt:P31 wd:Q47461344.
  }
  OPTIONAL {
    ?film wdt:P4947 "` + id + `";
      wdt:P144 ?bookseries.
    ?bookseries (wdt:P31/(wdt:P279*)) wd:Q277759.
  }
  OPTIONAL {
    ?serie wdt:P4983 "` + id + `";
      wdt:P144 ?bookseries.
    ?bookseries (wdt:P31/(wdt:P279*)) wd:Q277759.
  }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "` + tag + `", "en". }
}`)
	result.Result = res
	return gott.Tuple(args), err
}

func queryBook(args ...interface{}) (interface{}, error) {
	id := args[0].(*network.Request).Id
	tag := args[0].(*network.Request).Language[:2]
	result := args[1].(*Result)
	repo := result.Repo
	res, err := repo.Query(`SELECT ?title ?bookLabel ?authorLabel ?series ?seriesLabel ?ordinal ?genreLabel (YEAR(?publication) AS ?year) ?article_title WHERE {
  ` + id + ` wdt:P50 ?author.
  ` + id + ` wdt:P1476 ?title.
  optional {
    ` + id + ` wdt:P577 ?publication.
  }
  optional {
    ` + id + ` wdt:P136 ?genre.
  }
  OPTIONAL {
    ?article schema:about ` + id + `;
      schema:inLanguage "` + tag + `";
      schema:name ?article_title;
      schema:isPartOf _:b18.
    _:b18 wikibase:wikiGroup "wikipedia".
    FILTER(!(CONTAINS(?article_title, ":")))
  }
  OPTIONAL {
    ` + id + ` wdt:P179 ?series.
    OPTIONAL { ` + id + ` wdt:P1545 ?ordinal. }
  }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "` + tag + `", "en". }
  SERVICE wikibase:label {
    bd:serviceParam wikibase:language "` + tag + `", "en".
    ` + id + ` rdfs:label ?bookLabel.
  }
}`)
	result.Result = res
	return gott.Tuple(args), err
}

func parseBook(args ...interface{}) (interface{}, error) {
	id := args[0].(*network.Request).Id
	results := args[1].(*Result)
	res := results.Result
	if res.Results.Bindings == nil || len(res.Results.Bindings) == 0 {
		results.Work = &datastructure.Book{}
	} else {
		authors := map[string]bool{}
		genres := map[string]bool{}
		result := res.Results.Bindings[0]
		var year int64
		var err error
		if result["year"].Value != "" {
			year, err = strconv.ParseInt(result["year"].Value, 10, 64)
			if err != nil {
				return gott.Tuple(args), err
			}
		}
		bookId := id
		if result["book"].Value != "" {
			bookId = strings.Replace(result["book"].Value, "http://www.wikidata.org/entity/", "wd:", 1)
		}
		title := result["bookLabel"].Value
		if title == strings.Replace(bookId, "wd:", "", 1) || title == "" {
			title = result["title"].Value
		}
		book := datastructure.Book{
			Id:          bookId,
			Uri:         "/books/" + bookId,
			Source:      []datastructure.Source{},
			Title:       title,
			Year:        year,
			SerieUri:    strings.Replace(result["series"].Value, "http://www.wikidata.org/entity/", "/bookseries/wd:", 1),
			SerieName:   result["seriesLabel"].Value,
			PartInSerie: result["ordinal"].Value,
			Authors:     []string{},
			Genres:      []string{},
			Article:     result["article_title"].Value,
		}
		book.Source = append(book.Source, datastructure.Source{
			Url:  "https://www.wikidata.org/wiki/" + strings.Replace(id, "wd:", "", 1),
			Name: "Wikidata",
		})
		book.Source = append(book.Source, datastructure.Source{
			Url:  "https://inventaire.io/entity/" + id,
			Name: "Inventaire",
		})
		if result["bookseries"].Value != "" {
			book.Uri = strings.Replace(result["bookseries"].Value, "http://www.wikidata.org/entity/", "/bookseries/wd:", 1)
			book.Title = result["bookseriesLabel"].Value
		}
		for _, r := range res.Results.Bindings {
			authors[r["authorLabel"].Value] = true
			if r["genreLabel"].Value != "" {
				genres[r["genreLabel"].Value] = true
			}
		}
		for k := range authors {
			book.Authors = append(book.Authors, k)
		}
		for k := range genres {
			book.Genres = append(book.Genres, k)
		}
		results.Work = &book
	}
	return gott.Tuple(args), nil
}

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

func unmarshalIsbn(args ...interface{}) (interface{}, error) {
	var isbns struct{ Uris []string }
	result := args[1].(*network.Result)
	err := json.Unmarshal(result.Body, &isbns)
	uris := ""
	for _, isbn := range isbns.Uris {
		uris += "|" + isbn
	}
	if len(uris) > 0 {
		uris = uris[1:]
	}
	result.Result = uris
	return gott.Tuple(args), err
}

func getIsbn(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	res, err := gott.
		NewResult(gott.Tuple{request, &network.Result{}}).
		Bind(createIsbnRequest).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Bind(unmarshalIsbn).
		Finish()
	request.Id = res.(gott.Tuple)[1].(*network.Result).Result.(string)
	return gott.Tuple(args), err
}

func checkEmpty(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	if request.Id == "" {
		return gott.Tuple(args), errors.New("warning: empty ISBN")
	}
	return gott.Tuple(args), nil
}

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

func unmarshalEditions(args ...interface{}) (interface{}, error) {
	var editions Editions
	result := args[1].(*network.Result)
	err := json.Unmarshal(result.Body, &editions)
	result.Result = editions
	return gott.Tuple(args), err
}

func filterEditionLanguage(args ...interface{}) interface{} {
	result := args[1].(*network.Result)
	editions := result.Result.(Editions)
	language := args[0].(*network.Request).Language[:2]
	covers := []string{}
	englishCover := ""
	for _, edition := range editions.Entities {
		if edition.OriginalLang == language && edition.Image.Url != "" {
			covers = append(covers, "https://inventaire.io"+edition.Image.Url)
		}
		if edition.OriginalLang == "en" && englishCover == "" && edition.Image.Url != "" {
			englishCover = "https://inventaire.io" + edition.Image.Url
		}
	}
	if len(covers) == 0 {
		result.Result = englishCover
	} else {
		result.Result = covers[0]
	}
	return gott.Tuple(args)
}

func getCover(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	res, err := gott.
		NewResult(gott.Tuple{request, &network.Result{}}).
		Bind(createEditionsRequest).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Bind(unmarshalEditions).
		Map(filterEditionLanguage).
		Finish()
	args[1] = res.(gott.Tuple)[1].(*network.Result).Result.(string)
	return gott.Tuple(args), err
}

func GetBookByTmdb(id, language string) (datastructure.Book, error) {
	res, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &Result{}}).
		Bind(createRepo).
		Bind(queryBookByTmdb).
		Bind(parseBook).
		Finish()
	if err != nil {
		return datastructure.Book{}, err
	}
	book := res.(gott.Tuple)[1].(*Result).Work.(*datastructure.Book)
	return *book, nil
}

func GetBook(id, language string, connection *sql.DB) (*datastructure.Book, error) {
	res, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &Result{}}).
		Bind(createRepo).
		Bind(queryBook).
		Bind(parseBook).
		Finish()
	if err != nil {
		return &datastructure.Book{}, err
	}
	return res.(gott.Tuple)[1].(*Result).Work.(*datastructure.Book), nil
}

func GetCover(id, language string, connection *sql.DB) (string, error) {
	res, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language, Connection: connection}, ""}).
		Bind(getIsbn).
		Bind(checkEmpty).
		Bind(getCover).
		Finish()
	if err != nil && strings.HasPrefix(err.Error(), "warning") {
		return "", nil
	} else if err != nil {
		return "", err
	} else {
		return res.(gott.Tuple)[1].(string), nil
	}
}
