/*
    Copyright (C) 2020 Accurics, Inc.

	Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

		http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

package output

import (
	"strconv"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/service/ecr"
	grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
)

const (
	ecrPackageName    = "package_name"
	ecrPackageVersion = "package_version"
	ecrCvss           = "CVSS"
	ecrCvssVector     = "VECTOR"
	ecrCvssScore      = "SCORE"
	nvd               = "nvd"
	primaryURL        = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
)

// VendorCVSS holds cvss scoring vector for different vendors
type VendorCVSS map[string]CVSS

// Vulnerability holds vulnerability details of image
type Vulnerability struct {
	Target           string     `json:"target"`
	Type             string     `json:"type,omitempty"`
	VulnerabilityID  string     `json:"vulnerability_id,omitempty"`
	PkgName          string     `json:"pkg_name,omitempty"`
	InstalledVersion string     `json:"installed_version,omitempty"`
	FixedVersion     string     `json:"fixed_version,omitempty"`
	SeveritySource   string     `json:"severity_source,omitempty"`
	PrimaryURL       string     `json:"primary_url,omitempty"`
	Title            string     `json:"title,omitempty"`
	Description      string     `json:"description,omitempty"`
	Severity         string     `json:"severity,omitempty"`
	CweIDs           []string   `json:"cwe_ids,omitempty"`
	CVSS             VendorCVSS `json:"cvss,omitempty"`
	References       []string   `json:"references,omitempty"`
	PublishedDate    *time.Time `json:"published_date,omitempty"`
	LastModifiedDate *time.Time `json:"lastModified_date,omitempty"`
}

// CVSS holds score and vector details
type CVSS struct {
	V2Vector string  `json:"v2_vector,omitempty"`
	V3Vector string  `json:"v3_vector,omitempty"`
	V2Score  float64 `json:"v2_score,omitempty"`
	V3Score  float64 `json:"v3_score,omitempty"`
}

//ACRResponse holds response from ACR api call
type ACRResponse struct {
	Properties ACRVulnerabilityConfig `json:"properties"`
}

//ACRVulnerabilityConfig holds ACR api vulnerability information
type ACRVulnerabilityConfig struct {
	Description     string             `json:"description"`
	DisplayName     string             `json:"displayName"`
	ResourceDetails ACRResourceDetails `json:"resourceDetails"`
	Status          ACRStatus          `json:"status"`
	AdditionalData  ACRAdditionalData  `json:"additionalData"`
	TimeGenerated   time.Time          `json:"timeGenerated"`
	Remediation     string             `json:"remediation"`
	ID              string             `json:"id"`
	Category        string             `json:"category"`
	Impact          string             `json:"impact"`
}

// ACRResourceDetails holds ACR vulnerability resource details
type ACRResourceDetails struct {
	ID     string `json:"id"`
	Source string `json:"source"`
}

// ACRStatus holds  ACR vulnerability saverity details
type ACRStatus struct {
	Severity string `json:"severity"`
	Code     string `json:"code"`
}

// ACRAdditionalData holds ACR vulnerability's additional data
type ACRAdditionalData struct {
	AssessedResourceType string          `json:"assessedResourceType"`
	Type                 string          `json:"type"`
	VendorReferences     []ACRVendorInfo `json:"vendorReferences"`
	PublishedTime        time.Time       `json:"publishedTime"`
	Patchable            bool            `json:"patchable"`
	Cvss                 ACRCvss         `json:"cvss"`
	RepositoryName       string          `json:"repositoryName"`
	Cve                  []ACRCve        `json:"cve"`
	RegistryHost         string          `json:"registryHost"`
	ImageDigest          string          `json:"imageDigest"`
	CicdData             ACRCicdData     `json:"cicdData"`
}

// ACRVendorInfo holds vendor information
type ACRVendorInfo struct {
	Title string `json:"title"`
	Link  string `json:"link"`
}

// ACRCvss holds cvss score details in v2 and v3 vector
type ACRCvss struct {
	V2 ACRCvssBase `json:"2.0"`
	V3 ACRCvssBase `json:"3.0"`
}

//ACRCvssBase base holds the actual cvss score of vulnerability eg.  "base": 7.8
type ACRCvssBase struct {
	Base float64 `json:"base"`
}

// ACRCve holds cve details such as cve id and link to cve details
type ACRCve struct {
	Title string `json:"title"`
	Link  string `json:"link"`
}

//ACRCicdData holds information about the cicd job completion
type ACRCicdData struct {
	Status string `json:"status"`
}

// PrepareFromECRImageScanAttribute prepares cvss object from ECR image scan attribute
func (cvss *CVSS) PrepareFromECRImageScanAttribute(attribute *ecr.Attribute) {
	if strings.Contains(*attribute.Key, ecrCvss) {
		switch *attribute.Key {
		case ecrCvss + "2_" + ecrCvssVector:
			cvss.V2Vector = *attribute.Value
		case ecrCvss + "3_" + ecrCvssVector:
			cvss.V3Vector = *attribute.Value
		case ecrCvss + "2_" + ecrCvssScore:
			if v, err := strconv.ParseFloat(*attribute.Value, 64); err == nil {
				cvss.V2Score = v
			}
		case ecrCvss + "3_" + ecrCvssScore:
			if v, err := strconv.ParseFloat(*attribute.Value, 64); err == nil {
				cvss.V3Score = v
			}
		}
	}
}

// PrepareFromECRImageScan prepares vulnerability object from ECR image scan findings
func (v *Vulnerability) PrepareFromECRImageScan(imageScanFinding *ecr.ImageScanFinding) {
	if imageScanFinding.Severity != nil {
		v.Severity = strings.ToUpper(*imageScanFinding.Severity)
	}
	if imageScanFinding.Description != nil {
		v.Description = *imageScanFinding.Description
	}
	if imageScanFinding.Name != nil {
		v.VulnerabilityID = *imageScanFinding.Name
	}
	if imageScanFinding.Uri != nil {
		v.PrimaryURL = *imageScanFinding.Uri
	}
	if v.PrimaryURL == "" {
		v.PrimaryURL = primaryURL + v.VulnerabilityID
	}
	cvss := CVSS{}
	for _, attr := range imageScanFinding.Attributes {
		if attr.Key == nil || attr.Value == nil {
			continue
		} else if strings.EqualFold(*attr.Key, ecrPackageName) {
			v.PkgName = *attr.Value
		} else if strings.EqualFold(*attr.Key, ecrPackageVersion) {
			v.InstalledVersion = *attr.Value
		} else {
			cvss.PrepareFromECRImageScanAttribute(attr)
			vendorCVSS := make(VendorCVSS)
			vendorCVSS[nvd] = cvss
			v.CVSS = vendorCVSS
		}
	}
}

//PrepareFromACRImageScan - prepares vulnerability object from ACR image scan findings
func (v *Vulnerability) PrepareFromACRImageScan(acrResponse ACRResponse) {
	if acrResponse.Properties.ID == "" {
		return
	}
	v.Description = acrResponse.Properties.Description
	if acrResponse.Properties.AdditionalData.Cve != nil && len(acrResponse.Properties.AdditionalData.Cve) >= 1 {
		v.VulnerabilityID = acrResponse.Properties.AdditionalData.Cve[0].Title
		v.PrimaryURL = acrResponse.Properties.AdditionalData.Cve[0].Link
	}
	if v.PrimaryURL == "" {
		v.PrimaryURL = primaryURL + v.VulnerabilityID
	}

	v.Severity = strings.ToUpper(acrResponse.Properties.Status.Severity)
	vendorCVSS := make(VendorCVSS)
	cvss := CVSS{
		V2Score: acrResponse.Properties.AdditionalData.Cvss.V2.Base,
		V3Score: acrResponse.Properties.AdditionalData.Cvss.V3.Base,
	}
	vendorCVSS[nvd] = cvss
	v.CVSS = vendorCVSS
}

//PrepareFromGCRImageScan - prepares vulnerability object from GCP image scan findings
func (v *Vulnerability) PrepareFromGCRImageScan(gcpVulnerability *grafeaspb.Occurrence) {
	vul := gcpVulnerability.GetVulnerability()
	v.Type = vul.GetType()
	pkgIssue := vul.GetPackageIssue()
	for _, pkg := range pkgIssue {
		v.PkgName = pkg.GetAffectedPackage()
		v.InstalledVersion = pkg.GetAffectedVersion().GetFullName()
		v.FixedVersion = pkg.GetFixedVersion().GetFullName()
	}

	v.Severity = strings.ToUpper(vul.GetSeverity().String())
	v.Description = vul.GetLongDescription()
	noteURL := gcpVulnerability.GetNoteName()
	noteURLSlice := strings.Split(noteURL, "/")
	if len(noteURLSlice) > 0 {
		v.VulnerabilityID = noteURLSlice[len(noteURLSlice)-1]
	}
	for _, url := range vul.GetRelatedUrls() {
		v.References = append(v.References, url.GetUrl())
	}
	if v.PrimaryURL == "" {
		v.PrimaryURL = primaryURL + v.VulnerabilityID
	}
	cvss := CVSS{
		V2Score: float64(vul.GetCvssScore()),
	}
	vendorCVSS := make(VendorCVSS)
	vendorCVSS[nvd] = cvss
	v.CVSS = vendorCVSS
}
