package tools

import (
	"encoding/json"
	"fmt"
	"gitea.pena/SQuiz/common/model"
	"github.com/xuri/excelize/v2"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
)

const (
	bucketImages  = "squizimages"
	bucketFonts   = "squizfonts"
	bucketScripts = "squizscript"
	bucketStyle   = "squizstyle"
	bucketAnswers = "squizanswer"
)

func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []model.Answer, s3Prefix string) error {
	file := excelize.NewFile()
	sheet := "Sheet1"

	_, err := file.NewSheet(sheet)
	if err != nil {
		return err
	}

	sort.Slice(questions, func(i, j int) bool {
		return questions[i].Page < questions[j].Page
	})

	headers, mapQueRes := prepareHeaders(questions)
	headers = append([]string{"Дата и время"}, headers...)

	for col, header := range headers {
		cell := ToAlphaString(col+1) + "1"
		if err := file.SetCellValue(sheet, cell, header); err != nil {
			return err
		}
	}

	sort.Slice(answers, func(i, j int) bool {
		return answers[i].QuestionId < answers[j].QuestionId
	})
	standart, results := categorizeAnswers(answers)

	var wg sync.WaitGroup
	row := 2
	for session := range results {
		wg.Add(1)
		go func(session string, response []model.Answer, row int) {
			defer wg.Done()
			processSession(file, sheet, session, s3Prefix, response, results, questions, mapQueRes, headers, row)
		}(session, standart[session], row)
		row++
	}
	wg.Wait()

	return file.Write(buffer)
}

func prepareHeaders(questions []model.Question) ([]string, map[uint64]string) {
	headers := []string{"Данные респондента"}
	mapQueRes := make(map[uint64]string)

	for _, q := range questions {
		if !q.Deleted {
			if q.Type == model.TypeResult {
				mapQueRes[q.Id] = q.Title + "\n" + q.Description
			} else {
				headers = append(headers, q.Title)
			}
		}
	}
	headers = append(headers, "Результат")
	return headers, mapQueRes
}

func categorizeAnswers(answers []model.Answer) (map[string][]model.Answer, map[string]model.Answer) {
	standart := make(map[string][]model.Answer)
	results := make(map[string]model.Answer)

	for _, answer := range answers {
		if answer.Result {
			results[answer.Session] = answer
		} else {
			standart[answer.Session] = append(standart[answer.Session], answer)
		}
	}
	return standart, results
}

func processSession(file *excelize.File, sheet, session, s3Prefix string, response []model.Answer, results map[string]model.Answer, questions []model.Question, mapQueRes map[uint64]string, headers []string, row int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered from panic:", r)
		}
	}()

	if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].CreatedAt.Format("2006-01-02 15:04:05")); err != nil {
		fmt.Println(err.Error())
	}

	if err := file.SetCellValue(sheet, "B"+strconv.Itoa(row), results[session].Content); err != nil {
		fmt.Println(err.Error())
	}

	count := 3
	for _, q := range questions {
		if !q.Deleted && q.Type != model.TypeResult {
			cell := ToAlphaString(count) + strconv.Itoa(row)
			index := binarySearch(response, q.Id)
			if index != -1 {
				handleAnswer(file, sheet, cell, s3Prefix, response[index], q, count, row)
			} else {
				if err := file.SetCellValue(sheet, cell, "-"); err != nil {
					fmt.Println(err.Error())
				}
			}
			count++
		}
	}
	cell := ToAlphaString(len(headers)) + strconv.Itoa(row)
	if err := file.SetCellValue(sheet, cell, mapQueRes[results[session].QuestionId]); err != nil {
		fmt.Println(err.Error())
	}
}

func handleAnswer(file *excelize.File, sheet, cell, s3Prefix string, answer model.Answer, question model.Question, count, row int) {
	tipe := FileSearch(answer.Content)
	noAccept := make(map[string]struct{})
	todoMap := make(map[string]string)

	if tipe != "Text" && (question.Type == model.TypeImages || question.Type == model.TypeVarImages) {
		handleImage(file, sheet, cell, answer.Content, count, row, noAccept, todoMap, question.Title)
	} else if question.Type == model.TypeFile {
		handleFile(file, sheet, cell, answer.Content, s3Prefix, noAccept)
	} else {
		todoMap[answer.Content] = cell
	}

	for cnt, cel := range todoMap {
		if _, ok := noAccept[cnt]; !ok {
			cntArr := strings.Split(cnt, "`,`")
			resultCnt := cnt
			if len(cntArr) > 1 {
				resultCnt = strings.Join(cntArr, "\n")
			}

			if len(resultCnt) > 1 && resultCnt[0] == '`' && resultCnt[len(resultCnt)-1] == '`' {
				resultCnt = resultCnt[1 : len(resultCnt)-1]
			}

			if len(resultCnt) > 1 && resultCnt[0] == '`' {
				resultCnt = resultCnt[1:]
			}

			if len(resultCnt) > 1 && resultCnt[len(resultCnt)-1] == '`' {
				resultCnt = resultCnt[:len(resultCnt)-1]
			}

			if err := file.SetCellValue(sheet, cel, resultCnt); err != nil {
				fmt.Println(err.Error())
			}
		}
	}
}

func handleImage(file *excelize.File, sheet, cell, content string, count, row int, noAccept map[string]struct{}, todoMap map[string]string, questionTitle string) {
	multiImgArr := strings.Split(content, "`,`")
	if len(multiImgArr) > 1 {
		var descriptions []string
		mediaSheet := "Media"
		flag, err := file.GetSheetIndex(mediaSheet)
		if err != nil {
			fmt.Println(err.Error())
		}
		if flag == -1 {
			_, _ = file.NewSheet(mediaSheet)
			err = file.SetCellValue(mediaSheet, "A1", "Вопрос")
			if err != nil {
				fmt.Println(err.Error())
			}
		}

		mediaRow := row
		for i, imgContent := range multiImgArr {
			if i == 0 && len(imgContent) > 1 && imgContent[0] == '`' {
				imgContent = imgContent[1:]
			}

			if i == len(multiImgArr)-1 && len(imgContent) > 1 && imgContent[len(imgContent)-1] == '`' {
				imgContent = imgContent[:len(imgContent)-1]
			}

			var res model.ImageContent
			err := json.Unmarshal([]byte(imgContent), &res)
			if err != nil {
				res.Image = imgContent
			}

			// чек на пустой дескрипшен, есмли пустой то отмечаем как вариант ответа номер по i
			if res.Description != "" {
				descriptions = append(descriptions, res.Description)
			} else {
				descriptions = append(descriptions, fmt.Sprintf("Вариант ответа №%d", i+1))
			}

			urle := ExtractImageURL(res.Image)
			urlData := strings.Split(urle, " ")
			if len(urlData) == 1 {
				u, err := url.Parse(urle)
				if err == nil && u.Scheme != "" && u.Host != "" {
					picture, err := downloadImage(urle)
					if err != nil {
						fmt.Println(err.Error())
						continue
					}
					err = file.SetCellValue(mediaSheet, "A"+strconv.Itoa(mediaRow), questionTitle)
					if err != nil {
						fmt.Println(err.Error())
					}

					col := ToAlphaString(i + 2)
					err = file.SetColWidth(mediaSheet, col, col, 50)
					if err != nil {
						fmt.Println(err.Error())
					}
					err = file.SetRowHeight(mediaSheet, mediaRow, 150)
					if err != nil {
						fmt.Println(err.Error())
					}
					if err := file.AddPictureFromBytes(mediaSheet, col+strconv.Itoa(mediaRow), picture); err != nil {
						fmt.Println(err.Error())
					}
					noAccept[content] = struct{}{}
				} else {
					todoMap[content] = cell
				}
			} else {
				todoMap[imgContent] = cell
			}

			descriptionsStr := strings.Join(descriptions, "\n")
			linkText := fmt.Sprintf("%s\n Перейти в приложение %s!A%d", descriptionsStr, mediaSheet, mediaRow)

			if err := file.SetCellValue(sheet, cell, linkText); err != nil {
				fmt.Println(err.Error())
			}
			//if err := file.SetCellHyperLink(sheet, cell, fmt.Sprintf("%s!A%d", mediaSheet, mediaRow), "Location", excelize.HyperlinkOpts{
			//	Display: &linkText,
			//}); err != nil {
			//	fmt.Println(err.Error())
			//}
		}
	} else {
		if len(content) > 1 && content[0] == '`' && content[len(content)-1] == '`' {
			content = content[1 : len(content)-1]
		}
		var res model.ImageContent
		err := json.Unmarshal([]byte(content), &res)
		if err != nil {
			res.Image = content
		}
		urle := ExtractImageURL(res.Image)
		urlData := strings.Split(urle, " ")
		if len(urlData) == 1 {
			u, err := url.Parse(urle)
			if err == nil && u.Scheme != "" && u.Host != "" {
				picture, err := downloadImage(urle)
				if err != nil {
					fmt.Println(err.Error())
				}
				err = file.SetColWidth(sheet, ToAlphaString(count), ToAlphaString(count), 50)
				if err != nil {
					fmt.Println(err.Error())
				}
				err = file.SetRowHeight(sheet, row, 150)
				if err != nil {
					fmt.Println(err.Error())
				}
				if err := file.AddPictureFromBytes(sheet, cell, picture); err != nil {
					fmt.Println(err.Error())
				}
				noAccept[content] = struct{}{}
			} else {
				todoMap[content] = cell
			}
		} else {
			todoMap[content] = cell
		}
	}
}
func handleFile(file *excelize.File, sheet, cell, content, s3Prefix string, noAccept map[string]struct{}) {
	urle := content
	if urle != "" && !strings.HasPrefix(urle, "https") {
		urle = s3Prefix + urle
	}

	fmt.Println("ORRRRR", urle, s3Prefix)
	display, tooltip := urle, urle

	if err := file.SetCellValue(sheet, cell, urle); err != nil {
		fmt.Println(err.Error())
	}
	if err := file.SetCellHyperLink(sheet, cell, urle, "External", excelize.HyperlinkOpts{
		Display: &display,
		Tooltip: &tooltip,
	}); err != nil {
		fmt.Println(err.Error())
	}
	noAccept[content] = struct{}{}
}

func binarySearch(answers []model.Answer, questionID uint64) int {
	left := 0
	right := len(answers) - 1
	for left <= right {
		mid := left + (right-left)/2
		if answers[mid].QuestionId == questionID {
			return mid
		} else if answers[mid].QuestionId < questionID {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return -1
}

func FileSearch(content string) string {
	if strings.Contains(content, bucketImages) {
		return FileType(content)
	} else if strings.Contains(content, bucketFonts) {
		return FileType(content)
	} else if strings.Contains(content, bucketScripts) {
		return FileType(content)
	} else if strings.Contains(content, bucketStyle) {
		return FileType(content)
	} else if strings.Contains(content, bucketAnswers) {
		return FileType(content)
	}

	return "Text"
}

func FileType(filename string) string {
	parts := strings.Split(filename, ".")
	extension := parts[len(parts)-1]

	switch extension {
	case "png", "jpg", "jpeg", "gif", "bmp", "svg", "webp", "tiff", "ico":
		return "Image"
	default:
		return "File"
	}
}

func downloadImage(url string) (*excelize.Picture, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	defer func() {
		if derr := resp.Body.Close(); derr != nil {
			fmt.Printf("error close response body in downloadImage: %v", derr)
		}
	}()

	imgData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	ext := filepath.Ext(url)
	if ext == "" {
		contentType := resp.Header.Get("Content-Type")
		switch {
		case strings.HasPrefix(contentType, "image/jpeg"):
			ext = ".jpg"
		case strings.HasPrefix(contentType, "image/png"):
			ext = ".png"
		default:
			ext = ".png"
		}
	}

	pic := &excelize.Picture{
		Extension: ext,
		File:      imgData,
		Format: &excelize.GraphicOptions{
			AutoFit:     true,
			Positioning: "oneCell",
		},
	}
	return pic, nil
}

func ToAlphaString(col int) string {
	var result string
	for col > 0 {
		col--
		result = string(rune('A'+col%26)) + result
		col /= 26
	}
	return result
}

func ExtractImageURL(htmlContent string) string {
	re := regexp.MustCompile(`(?:<img[^>]*src="([^"]+)"[^>]*>)|(?:<td[^>]*>.*?<img[^>]*src="([^"]+)"[^>]*>.*?</td>)|(?:<tr[^>]*>.*?<td[^>]*>.*?<img[^>]*src="([^"]+)"[^>]*>.*?</td>.*?</tr>)|(?:<a[^>]*\s+download[^>]*>([^<]+)<\/a>)`)
	matches := re.FindAllStringSubmatch(htmlContent, -1)

	for _, match := range matches {
		for i := 1; i < len(match); i++ {
			if match[i] != "" {
				return strings.TrimSpace(match[i])
			}
		}
	}
	return htmlContent
}
