package nvd

import (
	"fmt"
	"strconv"
	"strings"

	c "github.com/kotakanbe/go-cve-dictionary/config"
	"github.com/kotakanbe/go-cve-dictionary/db"
	"github.com/kotakanbe/go-cve-dictionary/fetcher"
	log "github.com/kotakanbe/go-cve-dictionary/log"
	"github.com/kotakanbe/go-cve-dictionary/models"
)

// ListFetchedFeeds list fetched feeds information
func ListFetchedFeeds(driver db.DB) (jsonMetas, xmlMetas []models.FeedMeta, err error) {
	lastMetas, err := driver.GetFetchedFeedMetas()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to get Meta: %s", err)
	}
	if len(lastMetas) == 0 {
		log.Infof("No feeds found")
		return
	}

	xmlYears, jsonYears := map[int]bool{}, map[int]bool{}
	for _, meta := range lastMetas {
		if !checkNvdURL(meta.URL) {
			continue
		}
		y, xml, err := nvdFeedURLToYear(meta.URL)
		if err != nil {
			return nil, nil, err
		}
		if xml {
			xmlYears[y] = true
		} else {
			jsonYears[y] = true
		}
	}

	xmlY, jsonY := []int{}, []int{}
	for y := range xmlYears {
		xmlY = append(xmlY, y)
	}
	for y := range jsonYears {
		jsonY = append(jsonY, y)
	}

	xmlMetas, err = FetchLatestFeedMeta(driver, xmlY, true)
	if err != nil {
		return nil, nil, err
	}
	jsonMetas, err = FetchLatestFeedMeta(driver, jsonY, false)
	if err != nil {
		return nil, nil, err
	}
	return
}

func checkNvdURL(url string) bool {
	return strings.Contains(url, "nvdcve-")
}

func nvdFeedURLToYear(url string) (year int, xml bool, err error) {
	//TODO use meta.Year()
	yearstr := ""
	if strings.Contains(url, "nvdcve-2.0-") {
		xml = true
		yearstr = strings.TrimSuffix(strings.Split(url, "nvdcve-2.0-")[1], ".xml.gz")
	} else if strings.Contains(url, "nvdcve-1.0-") {
		yearstr = strings.TrimSuffix(strings.Split(url, "nvdcve-1.0-")[1], ".json.gz")
	} else {
		return year, xml, fmt.Errorf("Failed to parse URL: %s", url)
	}

	switch yearstr {
	case "recent", "modified":
		return c.Latest, xml, nil
	default:
		y, err := strconv.Atoi(yearstr)
		if err != nil {
			return 0, false, fmt.Errorf("Unable conver to int: %d, err: %s",
				year, err)
		}
		return y, xml, nil
	}
}

// FetchLatestFeedMeta fetches CVE meta information from NVD
func FetchLatestFeedMeta(driver db.DB, years []int, xml bool) (metas []models.FeedMeta, err error) {
	reqs := []fetcher.FetchRequest{}
	for _, year := range years {
		urls := MakeNvdMetaURLs(year, xml)
		for _, url := range urls {
			reqs = append(reqs, fetcher.FetchRequest{
				Year: year,
				URL:  url,
			})
		}
	}
	results, err := fetcher.FetchFeedFiles(reqs)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch. err: %s", err)
	}

	for _, res := range results {
		str := string(res.Body)
		ss := strings.Split(str, "\r\n")
		if len(ss) != 6 {
			continue
		}
		hash := ss[4]

		url := ""
		if xml {
			url = strings.Replace(res.URL, ".meta", ".xml.gz", -1)
		} else {
			url = strings.Replace(res.URL, ".meta", ".json.gz", -1)
		}

		meta, err := driver.GetFetchedFeedMeta(url)
		if err != nil {
			return nil, fmt.Errorf("Failed to get meta: %d, err: %s",
				res.Year, err)
		}
		meta.URL = url
		meta.LatestHash = hash
		meta.LatestLastModifiedDate = strings.TrimPrefix(ss[0], "lastModifiedDate:")
		metas = append(metas, *meta)
	}
	return
}

// UpdateMeta updates meta table
func UpdateMeta(driver db.DB, metas []models.FeedMeta) error {
	for _, meta := range metas {
		meta.Hash = meta.LatestHash
		meta.LastModifiedDate = meta.LatestLastModifiedDate
		err := driver.UpsertFeedHash(meta)
		if err != nil {
			return fmt.Errorf("Failed to updte meta: %s, err: %s",
				meta.URL, err)
		}
	}
	return nil
}

// MakeNvdMetaURLs returns a URL of NVD Feed
func MakeNvdMetaURLs(year int, xml bool) (url []string) {
	formatTemplate := ""
	if xml {
		// https://nvd.nist.gov/vuln/data-feeds#XML_FEED
		formatTemplate = "https://nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%s.meta"
	} else {
		// https: //nvd.nist.gov/vuln/data-feeds#JSON_FEED
		formatTemplate = "https://nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-%s.meta"
	}

	if year == c.Latest {
		for _, name := range []string{"modified", "recent"} {
			url = append(url, fmt.Sprintf(formatTemplate, name))
		}
	} else {
		feed := strconv.Itoa(year)
		url = append(url, fmt.Sprintf(formatTemplate, feed))
	}
	return
}
