package apksigner

import (
	"regexp"
	"strconv"
	"strings"
	"text/scanner"

	"notabug.org/Umnik/GoAndroidSDK/v2/components/misc"
)

var (
	signerNum     = regexp.MustCompile(`^Signer #(\d+)`)
	minSdkVersion = regexp.MustCompile(`^.+minSdkVersion=(\d+)`)
	maxSdkVersion = regexp.MustCompile(`^.+maxSdkVersion=(\d+)`)
)

// ParseDumpSignatureInfoFromBytes returns VerifyInfo from bytes
func ParseDumpSignatureInfoFromBytes(rawData []byte) VerifyInfo {
	return ParseDumpSignatureInfoFromString(string(rawData))
}

// boolExtractor extracts bool value from "arg:value" line
func boolExtractor(val string) bool {
	res, err := strconv.ParseBool(val)
	if err != nil {
		return false
	}
	return res
}

func getDN(line string) map[string]string {
	if strings.Contains(line, "\"") { // for example line contains `O="Ubiquiti Networks, Inc."`
		return getDNExtended(line)
	}

	value := strings.Split(line, ", ")
	res := make(map[string]string, len(value))
	for _, i := range value {
		parts := strings.SplitN(i, "=", 2)
		res[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
	}
	return res
}

func getDNExtended(line string) map[string]string {
	var scnr scanner.Scanner
	scnr.Init(strings.NewReader(line))

	result := map[string]string{}
	key := ""
	value := ""
	valueTrigger := false

	for token := scnr.Scan(); token != scanner.EOF; token = scnr.Scan() {
		switch token {
		case ',':
			valueTrigger = false
			key = ""
			value = ""
		case '=':
			valueTrigger = true
		default:
			if !valueTrigger {
				key += scnr.TokenText()
			} else {
				value += scnr.TokenText()
				if scnr.Peek() == ',' || scnr.Peek() == scanner.EOF {
					result[key] = value
				}
			}
		}
	}
	return result
}

func getNotProtectedFile(line string) string {
	return strings.TrimSpace(strings.SplitN(misc.TwoSpotValue(line), " ", 2)[0])
}

func getSingerNumber(line string) int {
	out := signerNum.FindStringSubmatch(line)
	return misc.IntExtractor(misc.RexSubStrSingleVal(out))
}

func getMinSdk(line string) int {
	out := minSdkVersion.FindStringSubmatch(line)
	return misc.IntExtractor(misc.RexSubStrSingleVal(out))
}

func getMaxSdk(line string) int {
	out := maxSdkVersion.FindStringSubmatch(line)
	return misc.IntExtractor(misc.RexSubStrSingleVal(out))
}

// ParseDumpSignatureInfoFromString returns VerifyInfo from string ers
func ParseDumpSignatureInfoFromString(rawData string) VerifyInfo {
	infoRes := VerifyInfo{SignInfo: []SignatureInfo{}}

	index := -1 //uses for v3.1 only
	for _, rawLine := range strings.Split(rawData, "\n") {
		rawLine = strings.TrimSpace(rawLine)

		switch {
		case strings.Contains(rawLine, "using v1 scheme"):
			infoRes.V1 = boolExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.Contains(rawLine, "using v2 scheme"):
			infoRes.V2 = boolExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.Contains(rawLine, "using v3 scheme"):
			infoRes.V3 = boolExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.Contains(rawLine, "using v3.1 scheme"):
			infoRes.V3_1 = boolExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.Contains(rawLine, "using v4 scheme"):
			infoRes.V4 = boolExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "certificate DN"):
			// This is first line and we can use append
			infoRes.SignInfo = append(infoRes.SignInfo, SignatureInfo{CertDN: getDN(misc.TwoSpotRexValue(rawLine))})

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "certificate SHA-256 digest"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].CertSHA256 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "certificate SHA-1 digest"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].CertSHA1 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "certificate MD5 digest"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].CertMD5 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "key algorithm"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].KeyAlgorithm = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "key size (bits)"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].KeySize = misc.IntExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "public key SHA-256 digest"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].PubKeySHA256 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "public key SHA-1 digest"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].PubKeySHA1 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer #") && strings.Contains(rawLine, "public key MD5 digest"):
			infoRes.SignInfo[getSingerNumber(rawLine)-1].PubKeyMD5 = misc.TwoSpotRexValue(rawLine)

		case strings.Contains(rawLine, "not protected by signature"):
			infoRes.NotProtected = append(infoRes.NotProtected, getNotProtectedFile(rawLine))

		// for v3.1 only
		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "certificate DN"):
			index += 1
			// This is first line and we can use append
			infoRes.SignInfo = append(infoRes.SignInfo, SignatureInfo{CertDN: getDN(misc.TwoSpotRexValue(rawLine))})
			infoRes.SignInfo[index].MinSdk = getMinSdk(rawLine)
			infoRes.SignInfo[index].MaxSdk = getMaxSdk(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "certificate SHA-256 digest"):
			infoRes.SignInfo[index].CertSHA256 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "certificate SHA-1 digest"):
			infoRes.SignInfo[index].CertSHA1 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "certificate MD5 digest"):
			infoRes.SignInfo[index].CertMD5 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "key algorithm"):
			infoRes.SignInfo[index].KeyAlgorithm = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "key size (bits)"):
			infoRes.SignInfo[index].KeySize = misc.IntExtractor(misc.TwoSpotRexValue(rawLine))

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "public key SHA-256 digest"):
			infoRes.SignInfo[index].PubKeySHA256 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "public key SHA-1 digest"):
			infoRes.SignInfo[index].PubKeySHA1 = misc.TwoSpotRexValue(rawLine)

		case strings.HasPrefix(rawLine, "Signer (minSdkVersion=") && strings.Contains(rawLine, "public key MD5 digest"):
			infoRes.SignInfo[index].PubKeyMD5 = misc.TwoSpotRexValue(rawLine)
		}
	}

	return infoRes
}
