package xml

import (
	"encoding/xml"
	"fmt"
	"time"

	"github.com/kotakanbe/go-cve-dictionary/fetcher"
	"github.com/kotakanbe/go-cve-dictionary/models"
)

// NvdXML is array of Entry
type NvdXML struct {
	Entries []Entry `xml:"entry"`
}

// Entry is Root Element
type Entry struct {
	CveID            string      `xml:"id,attr" json:"id"`
	PublishedDate    time.Time   `xml:"published-datetime"`
	LastModifiedDate time.Time   `xml:"last-modified-datetime"`
	Cvss             Cvss        `xml:"cvss>base_metrics" json:"cvss"`
	Products         []string    `xml:"vulnerable-software-list>product"` //CPE
	Summary          string      `xml:"summary"`
	References       []Reference `xml:"references"`
	Cwes             []Cwe       `xml:"cwe"`
}

// Cvss is Cvss Score
type Cvss struct {
	Score                 string    `xml:"score"`
	AccessVector          string    `xml:"access-vector"`
	AccessComplexity      string    `xml:"access-complexity"`
	Authentication        string    `xml:"authentication"`
	ConfidentialityImpact string    `xml:"confidentiality-impact"`
	IntegrityImpact       string    `xml:"integrity-impact"`
	AvailabilityImpact    string    `xml:"availability-impact"`
	Source                string    `xml:"source"`
	GeneratedOnDate       time.Time `xml:"generated-on-datetime"`
}

// Cwe has Cwe ID
type Cwe struct {
	ID string `xml:"id,attr"`
}

// Reference is additional information about the CVE
type Reference struct {
	Type   string `xml:"reference_type,attr"`
	Source string `xml:"source"`
	Link   Link   `xml:"reference"`
}

// Link is additional information about the CVE
type Link struct {
	Value string `xml:",chardata" json:"value"`
	Href  string `xml:"href,attr" json:"href"`
}

// FetchConvert Fetch CVE vulnerability informatino from JVN
func FetchConvert(metas []models.FeedMeta) (cves []models.CveDetail, err error) {
	reqs := []fetcher.FetchRequest{}
	for _, meta := range metas {
		reqs = append(reqs, fetcher.FetchRequest{
			URL:  meta.URL,
			GZIP: true,
		})
	}

	results, err := fetcher.FetchFeedFiles(reqs)
	if err != nil {
		return nil,
			fmt.Errorf("Failed to fetch. err: %s", err)
	}

	for _, res := range results {
		nvd := NvdXML{}
		if err = xml.Unmarshal(res.Body, &nvd); err != nil {
			return nil, fmt.Errorf(
				"Failed to unmarshal. url: %s, err: %s",
				res.URL, err)
		}
		for _, e := range nvd.Entries {
			cve, err := convertToModel(e)
			if err != nil {
				return nil, fmt.Errorf("Failed to convert to model. cve: %s, err: %s",
					e.CveID, err)
			}
			cves = append(cves, *cve)
		}
	}
	return
}

// converToModel converts Nvd structure(got from NVD) to model structure.
func convertToModel(entry Entry) (cves *models.CveDetail, err error) {
	refs := []models.Reference{}
	for _, r := range entry.References {
		ref := models.Reference{
			Source: r.Source,
			Link:   r.Link.Href,
		}
		refs = append(refs, ref)
	}

	// uncomment if you needed
	// cpes := []models.Cpe{}
	// for _, c := range entry.Products {
	// cpeBase, err := fetcher.ParseCpeURI(c)
	// if err != nil {
	// return nil, err
	// }
	// cpes = append(cpes, models.Cpe{
	// CpeBase: *cpeBase,
	// })
	// }

	cwes := []models.Cwe{}
	for _, cwe := range entry.Cwes {
		cwes = append(cwes, models.Cwe{
			CweID: cwe.ID,
		})
	}

	score := fetcher.StringToFloat(entry.Cvss.Score)

	vectorString := ""
	if entry.Cvss.AccessVector != "" {
		vectorString = fmt.Sprintf("AV:%c/AC:%c/Au:%c/C:%c/I:%c/A:%c",
			entry.Cvss.AccessVector[0],
			entry.Cvss.AccessComplexity[0],
			entry.Cvss.Authentication[0],
			entry.Cvss.ConfidentialityImpact[0],
			entry.Cvss.IntegrityImpact[0],
			entry.Cvss.AvailabilityImpact[0],
		)
	}

	return &models.CveDetail{
		CveID: entry.CveID,
		NvdXML: &models.NvdXML{
			CveID:   entry.CveID,
			Summary: entry.Summary,
			Cvss2: models.Cvss2{
				BaseScore:             score,
				Severity:              cvss2ScoreToSeverity(score),
				VectorString:          vectorString,
				AccessVector:          entry.Cvss.AccessVector,
				AccessComplexity:      entry.Cvss.AccessComplexity,
				Authentication:        entry.Cvss.Authentication,
				ConfidentialityImpact: entry.Cvss.ConfidentialityImpact,
				IntegrityImpact:       entry.Cvss.IntegrityImpact,
				AvailabilityImpact:    entry.Cvss.AvailabilityImpact,
			},
			Cwes:             cwes,
			PublishedDate:    entry.PublishedDate,
			LastModifiedDate: entry.LastModifiedDate,

			// Cpes:       cpes,
			References: refs,
		},
	}, nil
}

// https://nvd.nist.gov/vuln-metrics/cvss
func cvss2ScoreToSeverity(score float64) string {
	if 7.0 <= score {
		return "HIGH"
	} else if 4.0 <= score {
		return "MEDIUM"
	} else {
		return "LOW"
	}
}
