package wikidata

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

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

	"notabug.org/apiote/gott"
)

type Ordinals struct {
	Entities map[string]struct {
		Claims struct {
			P527 []struct {
				Mainsnak struct {
					Datavalue struct {
						Value struct {
							Id string
						}
					}
				}
				Qualifiers struct {
					P1545 []struct {
						Datavalue struct {
							Value string
						}
					}
				}
			}
		}
	}
}

type BookSeriePart struct {
	Cover   string
	Ordinal string
	Title   string
	Uri     string
}

type BookSerie struct {
	Title       string
	Cover       string // note maybe first book’s cover?
	Description string
	Authors     []string
	Genres      []string
	Source      []datastructure.Source
	Article     string
	Parts       map[string]BookSeriePart
	SortedParts []BookSeriePart
}

func (s BookSerie) GetArticle() string {
	return s.Article
}

func (s *BookSerie) SetDescription(description string) {
	s.Description = description
}

func queryBookSerie(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 ?seriesLabel ?genreLabel ?authorLabel ?partLabel ?partTitle ?part ?article_title WHERE {
  ` + id + ` wdt:P50 ?author;
             wdt:P1476 ?title;
             wdt:P527 ?part.
  ?part wdt:P1476 ?partTitle.
  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, ":")))
  }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "` + tag + `", "en". }
  SERVICE wikibase:label {
    bd:serviceParam wikibase:language "` + tag + `", "en".
    ` + id + ` rdfs:label ?seriesLabel.
  }
}`)
	result.Result = res
	return gott.Tuple(args), err
}

func parseBookSerie(args ...interface{}) interface{} {
	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 = &BookSerie{}
	} else {
		authors := map[string]bool{}
		genres := map[string]bool{}
		result := res.Results.Bindings[0]
		title := result["bookLabel"].Value
		if title == strings.Replace(id, "wd:", "", 1) || title == "" {
			title = result["title"].Value
		}
		bookSerie := BookSerie{
			Title:   title,
			Source:  []datastructure.Source{},
			Authors: []string{},
			Genres:  []string{},
			Article: result["article_title"].Value,
			Parts:   map[string]BookSeriePart{},
		}
		for _, r := range res.Results.Bindings {
			authors[r["authorLabel"].Value] = true
			if r["genreLabel"].Value != "" {
				genres[r["genreLabel"].Value] = true
			}
			title := r["partLabel"].Value
			if title == strings.Replace(r["part"].Value, "http://www.wikidata.org/entity/", "", 1) || title == "" {
				title = r["partTitle"].Value
			}
			partId := strings.Replace(r["part"].Value, "http://www.wikidata.org/entity/", "", 1)
			bookSerie.Parts[partId] = BookSeriePart{
				Uri:   strings.Replace(r["part"].Value, "http://www.wikidata.org/entity/", "/books/wd:", 1),
				Title: title,
			}
		}
		bookSerie.Source = append(bookSerie.Source, datastructure.Source{
			Url:  "https://www.wikidata.org/wiki/" + strings.Replace(id, "wd:", "", 1),
			Name: "Wikidata",
		})
		bookSerie.Source = append(bookSerie.Source, datastructure.Source{
			Url:  "https://inventaire.io/entity/" + id,
			Name: "Inventaire",
		})

		for k := range authors {
			bookSerie.Authors = append(bookSerie.Authors, k)
		}
		for k := range genres {
			bookSerie.Genres = append(bookSerie.Genres, k)
		}
		results.Work = &bookSerie
	}
	return gott.Tuple(args)
}

func createOrdinalsRequest(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	id := strings.Replace(request.Id, "wd:", "", 1)
	client := &http.Client{}
	httpRequest, err := http.NewRequest("GET", "https://www.wikidata.org/wiki/Special:EntityData/"+id+".json", nil)
	result.Client = client
	result.Request = httpRequest
	return gott.Tuple(args), err
}

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

func sortOrdinals(args ...interface{}) (interface{}, error) {
	bookSerie := args[2].(*BookSerie)
	ordinals := args[1].(*network.Result).Result.(Ordinals).Entities
	sorted := []BookSeriePart{}
	for _, entity := range ordinals {
		for _, claim := range entity.Claims.P527 {
			id := claim.Mainsnak.Datavalue.Value.Id
			part := bookSerie.Parts[id]
			if len(claim.Qualifiers.P1545) > 0 {
				ordinal := claim.Qualifiers.P1545[0].Datavalue.Value
				part.Ordinal = ordinal
			} else {
				part.Ordinal = ""
			}
			sorted = append(sorted, part)
		}
	}
	sort.Slice(sorted, func(i, j int) bool {
		if sorted[i].Ordinal == "prologue" || sorted[j].Ordinal == "epilogue" {
			return true
		} else if sorted[i].Ordinal == "epilogue" || sorted[j].Ordinal == "prologue" {
			return false
		} else {
			return sorted[i].Ordinal < sorted[j].Ordinal
		}
	})
	bookSerie.SortedParts = sorted
	return gott.Tuple(args), nil
}

func GetBookSerie(id, language string, connection *sql.DB) (*BookSerie, error) {
	res, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language, Connection: connection}, &Result{}}).
		Bind(createRepo).
		Bind(queryBookSerie).
		Map(parseBookSerie).
		Finish()
	if err != nil {
		return &BookSerie{}, err
	}
	return res.(gott.Tuple)[1].(*Result).Work.(*BookSerie), nil
}

func GetBookSerieOrdinals(id string, bookSerie *BookSerie, connection *sql.DB) (*BookSerie, error) {
	_, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Connection: connection}, &network.Result{}, bookSerie}).
		Bind(createOrdinalsRequest).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Bind(unmarshalOrdinals).
		Bind(sortOrdinals).
		// todo add is_read in parts
		Finish()
	if err != nil {
		return &BookSerie{}, err
	}
	return bookSerie, nil
}
