package handler

import (
	"context"
	"fmt"
	"net/url"

	"github.com/micro/micro/v3/service/errors"
	"github.com/micro/micro/v3/service/logger"
	"github.com/micro/services/pkg/api"
	"github.com/micro/services/quran/domain"
	pb "github.com/micro/services/quran/proto"
)

const (
	// the default api url
	apiUrl = "https://api.quran.com/api/v4/"
	// TODO: allow multiple translations
	// the default translation id
	translationId = "131,20"
	// TODO: allow multiple interpretations
	// the default tafsir id
	tafsirId = "169"
	// TODO: make configurable
	arabicText = "text_imlaei"
)

type Quran struct{}

// Chapters returns a list of the chapters of the Quran
func (q *Quran) Chapters(ctx context.Context, req *pb.ChaptersRequest, rsp *pb.ChaptersResponse) error {
	lang := "en"
	if len(req.Language) > 0 {
		lang = req.Language
	}

	var resp map[string][]*domain.Chapter
	if err := api.Get(apiUrl+"chapters?language="+lang, &resp); err != nil {
		logger.Errorf("Failed to retrieve chapters: %v", err)
		return errors.InternalServerError("quran.chapters", "Failed to retrieve chapters")
	}

	for _, c := range resp["chapters"] {
		rsp.Chapters = append(rsp.Chapters, &pb.Chapter{
			Id:              c.Id,
			RevelationPlace: c.RevelationPlace,
			RevelationOrder: c.RevelationOrder,
			PrefixBismillah: c.BismillahPrefix,
			Name:            c.NameSimple,
			ComplexName:     c.NameComplex,
			ArabicName:      c.NameArabic,
			TranslatedName:  c.TranslatedName.Name,
			Verses:          c.VersesCount,
			Pages:           c.Pages,
		})
	}

	return nil
}

// Retrieve the summary for a given chapter
func (q *Quran) Summary(ctx context.Context, req *pb.SummaryRequest, rsp *pb.SummaryResponse) error {
	lang := "en"
	if len(req.Language) > 0 {
		lang = req.Language
	}
	if req.Chapter <= 0 {
		return errors.BadRequest("quran.chapter-summary", "require chapter id")
	}

	var resp map[string]*domain.ChapterInfo
	uri := fmt.Sprintf(apiUrl+"chapters/%d/info?language=%s", req.Chapter, lang)

	if err := api.Get(uri, &resp); err != nil {
		logger.Errorf("Failed to retrieve chapter info: %v", err)
		return errors.InternalServerError("quran.chapter-summary", "Failed to retrieve chapter summary")
	}

	info := resp["chapter_info"]
	rsp.Chapter = info.ChapterId
	rsp.Summary = info.ShortText
	rsp.Source = info.Source
	rsp.Text = info.Text

	return nil
}

// Return the verses by chapter
func (q *Quran) Verses(ctx context.Context, req *pb.VersesRequest, rsp *pb.VersesResponse) error {
	lang := "en"
	if len(req.Language) > 0 {
		lang = req.Language
	}

	if req.Chapter <= 0 {
		return errors.BadRequest("quran.verses", "require chapter id")
	}

	// TODO: enable configuring translations
	// comma separated list of resource ids
	// https://quran.api-docs.io/v4/resources/translations
	translations := translationId
	// TODO: enable configuring tafirs
	// https://api.quran.com/api/v4/resources/tafsirs
	tafsirs := tafsirId

	uri := fmt.Sprintf(apiUrl+"verses/by_chapter/%d?language=%s", req.Chapter, lang)

	// additional fields we require
	// arabic text in imlaei script
	uri += "&fields=" + arabicText

	uri += "&words=true"
	uri += "&word_fields=code_v2,text_imlaei"

	if len(translations) > 0 && req.Translate {
		uri += "&translations=" + translations
		uri += "&translation_fields=resource_name"
	}

	if len(tafsirs) > 0 && req.Interpret {
		uri += "&tafsirs=" + tafsirs
	}

	if req.Page > 0 {
		uri += fmt.Sprintf("&page=%d", req.Page)
	}

	if req.Limit > 0 {
		uri += fmt.Sprintf("&per_page=%d", req.Limit)
	}

	var resp *domain.VersesByChapter

	if err := api.Get(uri, &resp); err != nil {
		logger.Errorf("Failed to retrieve verses: %v", err)
		return errors.InternalServerError("quran.verses", "Failed to retrieve verses")
	}

	rsp.Chapter = req.Chapter
	rsp.Page = resp.Pagination.CurrentPage
	rsp.TotalPages = resp.Pagination.TotalPages

	for _, verse := range resp.Verses {
		v := domain.VerseToProto(verse)
		// strip words if not asked for
		if req.Words != true {
			v.Words = nil
		}
		rsp.Verses = append(rsp.Verses, v)
	}

	return nil
}

// Return the search results for a given query
func (q *Quran) Search(ctx context.Context, req *pb.SearchRequest, rsp *pb.SearchResponse) error {
	if len(req.Query) == 0 {
		return errors.BadRequest("quran.search", "missing search query")
	}

	lang := "en"
	if len(req.Language) > 0 {
		lang = req.Language
	}

	if req.Limit <= 0 {
		req.Limit = 20
	}

	qq := url.Values{}
	qq.Set("q", req.Query)
	qq.Set("size", fmt.Sprintf("%d", req.Limit))
	qq.Set("page", fmt.Sprintf("%d", req.Page))
	qq.Set("language", lang)

	uri := fmt.Sprintf(apiUrl+"search?%s", qq.Encode())

	var resp map[string]*domain.SearchResults

	if err := api.Get(uri, &resp); err != nil {
		logger.Errorf("Failed to retrieve search results: %v", err)
		return errors.InternalServerError("quran.search", "Failed to retrieve search results")
	}

	rsp.Query = req.Query
	rsp.Page = resp["search"].CurrentPage
	rsp.TotalPages = resp["search"].TotalPages
	rsp.TotalResults = resp["search"].TotalResults

	for _, result := range resp["search"].Results {
		r := domain.ResultToProto(result)
		rsp.Results = append(rsp.Results, r)
	}

	return nil
}
