import { parentPort } from 'worker_threads'
import {
    Question,
    QuestionData,
    QuestionDb,
    Subject,
} from '../../types/basicTypes'
import logger from '../../utils/logger'
import {
    SearchResultQuestion,
    getSubjNameWithoutYear,
    minMatchToNotSearchOtherSubjects,
    noPossibleAnswerMatchPenalty,
    prepareQuestion,
    searchSubject,
} from '../../utils/qdbUtils'
import { recognizeTextFromBase64 } from '../../utils/tesseract'
import { WorkerResult } from '../worker'

export type SearchTaskObject = {
    type: 'search'
    data: {
        searchIn: number[]
        question: Question
        subjName: string
        testUrl?: string
        questionData?: QuestionData
        searchInAllIfNoResult?: boolean
        searchTillMatchPercent?: number
        [key: string]: any
    }
}

export function doSearch(
    data: Array<Subject>,
    subjName: string,
    question: Question,
    searchTillMatchPercent?: number,
    searchInAllIfNoResult?: Boolean
): SearchResultQuestion[] {
    let result: SearchResultQuestion[] = []

    const questionToSearch = prepareQuestion(question)

    data.every((subj) => {
        if (
            subjName
                .toLowerCase()
                .includes(getSubjNameWithoutYear(subj.Name).toLowerCase())
        ) {
            const subjRes = searchSubject(
                subj,
                questionToSearch,
                subjName,
                searchTillMatchPercent
            )
            result = result.concat(subjRes)
            if (searchTillMatchPercent) {
                return !subjRes.some((sr) => {
                    return sr.match >= searchTillMatchPercent
                })
            }
            return true
        }
        return true
    })

    if (searchInAllIfNoResult) {
        // FIXME: dont research subject searched above
        if (
            result.length === 0 ||
            result[0].match < minMatchToNotSearchOtherSubjects
        ) {
            data.every((subj) => {
                const subjRes = searchSubject(
                    subj,
                    questionToSearch,
                    subjName,
                    searchTillMatchPercent
                )
                result = result.concat(subjRes)

                if (searchTillMatchPercent) {
                    const continueSearching = !subjRes.some((sr) => {
                        return sr.match >= searchTillMatchPercent
                    })
                    return continueSearching
                }
                return true
            })
        }
    }

    result = setNoPossibleAnswersPenalties(
        questionToSearch.data.possibleAnswers,
        result
    )

    result = result.sort((q1, q2) => {
        if (q1.match < q2.match) {
            return 1
        } else if (q1.match > q2.match) {
            return -1
        } else {
            return 0
        }
    })

    return result
}

export function setNoPossibleAnswersPenalties(
    questionPossibleAnswers: QuestionData['possibleAnswers'],
    results: SearchResultQuestion[]
): SearchResultQuestion[] {
    if (!Array.isArray(questionPossibleAnswers)) {
        return results
    }
    const noneHasPossibleAnswers = results.every((x) => {
        return !Array.isArray(x.q.data.possibleAnswers)
    })
    if (noneHasPossibleAnswers) return results

    let possibleAnswerMatch = false
    const updated = results.map((result) => {
        const matchCount = Array.isArray(result.q.data.possibleAnswers)
            ? result.q.data.possibleAnswers.filter((resultPossibleAnswer) => {
                  return questionPossibleAnswers.some(
                      (questionPossibleAnswer) => {
                          if (
                              questionPossibleAnswer.val &&
                              resultPossibleAnswer.val
                          ) {
                              return questionPossibleAnswer.val.includes(
                                  resultPossibleAnswer.val
                              )
                          } else {
                              return false
                          }
                      }
                  )
              }).length
            : 0

        if (matchCount === questionPossibleAnswers.length) {
            possibleAnswerMatch = true
            return result
        } else {
            return {
                ...result,
                match: result.match - noPossibleAnswerMatchPenalty,
                detailedMatch: {
                    ...result.detailedMatch,
                    qMatch:
                        result.detailedMatch.qMatch -
                        noPossibleAnswerMatchPenalty,
                },
            }
        }
    })

    if (possibleAnswerMatch) {
        return updated
    } else {
        return results
    }
}

async function recognizeQuestionImage(question: Question): Promise<Question> {
    const base64Data = question.data.base64
    if (Array.isArray(base64Data) && base64Data.length) {
        const res: string[] = []
        for (let i = 0; i < base64Data.length; i++) {
            const base64 = base64Data[i]
            const text = await recognizeTextFromBase64(base64)
            if (text && text.trim()) {
                res.push(text)
            }
        }

        if (res.length) {
            return {
                ...question,
                Q: res.join(' '),
                data: {
                    ...question.data,
                    type: 'simple',
                },
            }
        }
    }

    return question
}

export const handleSearch = async (
    qdbs: QuestionDb[],
    msg: SearchTaskObject,
    workerIndex: number
): Promise<void> => {
    const {
        subjName,
        question: originalQuestion,
        searchTillMatchPercent,
        searchInAllIfNoResult,
        searchIn,
        index,
    } = msg.data

    let searchResult: SearchResultQuestion[] = []
    let error = false

    const question = await recognizeQuestionImage(originalQuestion)

    try {
        qdbs.forEach((qdb) => {
            if (searchIn.includes(qdb.index)) {
                const res = doSearch(
                    qdb.data,
                    subjName,
                    question,
                    searchTillMatchPercent,
                    searchInAllIfNoResult
                )
                searchResult = [
                    ...searchResult,
                    ...res.map((x) => {
                        return {
                            ...x,
                            detailedMatch: {
                                ...x.detailedMatch,
                                qdb: qdb.name,
                            },
                        }
                    }),
                ]
            }
        })
    } catch (err) {
        logger.Log('Error in worker thread!', logger.GetColor('redbg'))
        console.error(err)
        console.error(
            JSON.stringify(
                {
                    subjName: subjName,
                    question: question,
                    searchTillMatchPercent: searchTillMatchPercent,
                    searchInAllIfNoResult: searchInAllIfNoResult,
                    searchIn: searchIn,
                    index: index,
                },
                null,
                2
            )
        )
        error = true
    }

    // sorting
    const sortedResult: SearchResultQuestion[] = searchResult.sort((q1, q2) => {
        if (q1.match < q2.match) {
            return 1
        } else if (q1.match > q2.match) {
            return -1
        } else {
            return 0
        }
    })

    const workerResult: WorkerResult = {
        msg: `From thread #${workerIndex}: job ${
            !isNaN(index) ? `#${index}` : ''
        }done`,
        workerIndex: workerIndex,
        result: sortedResult,
        error: error,
    }

    parentPort.postMessage(workerResult)
}
