/*
 * Copyright 2021-2024 JetBrains s.r.o.
 *
 * 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
 *
 * https://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 thirdpartyscan

import (
	"regexp"

	"github.com/JetBrains/qodana-cli/internal/platform/product"
	"github.com/JetBrains/qodana-cli/internal/platform/qdyaml"
)

const Clang = "clang"
const Clt = "clt"

type ThirdPartyStartupCloudData struct {
	LicensePlan   string
	ProjectIdHash string
	QodanaToken   string
}

// MountInfo is a struct that contains all the helper tools to run a Qodana linter.
type MountInfo struct {
	Converter   string
	Fuser       string
	BaselineCli string
	CustomTools map[string]string
	JavaPath    string
}

// LinterInfo is a struct that contains all the information about the linter.
type LinterInfo struct {
	ProductCode           string
	LinterPresentableName string
	LinterName            string
	LinterVersion         string
	IsEap                 bool
}

// Context
//
// If one has the instance of Context, then it means that it was initialized, and it is in valid state
// all mutations should be defined in context_changes.go with names clearly demonstrating the usecase and business logic.
// example: scoped script launches two stages of default analysis
//
// # In the future, you can consider making it an interface
//
// Yes, there is a lot of boilerplate, and it's ok, it's much better than having one object written and initialized across your program
// if I have an object it means it must be in valid state, otherwise it's impossible to reason about the code
//
// Q: - Why do we prohibit mutations in Context if we pass it by value already?
// A: - Because we want to prohibit any unexpected changes to Context at all, imagine at some place the projectDir will
//
//	suddenly change, and pass it further? It's not clear why it was changed? Was it actually initialized at this place?
//	but it must be initialized already, so we limit all changes, and keep them in context_changes.go
//
// !!!KEEP IT IMMUTABLE!!!
type Context struct {
	linterInfo                LinterInfo
	mountInfo                 MountInfo
	cloudData                 ThirdPartyStartupCloudData
	projectDir                string
	resultsDir                string
	reportDir                 string
	logDir                    string
	cacheDir                  string
	clangCompileCommands      string
	clangArgs                 string
	property                  []string
	cdnetSolution             string
	cdnetProject              string
	cdnetConfiguration        string
	cdnetPlatform             string
	noStatistics              bool
	cdnetNoBuild              bool
	analysisId                string
	baseline                  string
	baselineIncludeAbsent     bool
	failThreshold             string
	generateCodeClimateReport bool
	sendBitBucketInsights     bool
	qodanaYamlConfig          QodanaYamlConfig
}

type QodanaYamlConfig struct {
	Bootstrap         string
	Version           string
	DotNet            qdyaml.DotNet
	Includes          []qdyaml.Clude
	Excludes          []qdyaml.Clude
	FailThreshold     *int
	FailureConditions qdyaml.FailureConditions
}

func YamlConfig(yaml qdyaml.QodanaYaml) QodanaYamlConfig {
	return QodanaYamlConfig{
		Bootstrap:         yaml.Bootstrap,
		Version:           yaml.Version,
		DotNet:            yaml.DotNet,
		Includes:          yaml.Includes,
		Excludes:          yaml.Excludes,
		FailThreshold:     yaml.FailThreshold,
		FailureConditions: yaml.FailureConditions,
	}
}

type ContextBuilder struct {
	LinterInfo                LinterInfo
	MountInfo                 MountInfo
	CloudData                 ThirdPartyStartupCloudData
	ProjectDir                string
	ResultsDir                string
	ReportDir                 string
	LogDir                    string
	CacheDir                  string
	ClangCompileCommands      string
	ClangArgs                 string
	Property                  []string
	CdnetSolution             string
	CdnetProject              string
	CdnetConfiguration        string
	CdnetPlatform             string
	NoStatistics              bool
	CdnetNoBuild              bool
	AnalysisId                string
	Baseline                  string
	BaselineIncludeAbsent     bool
	FailThreshold             string
	GenerateCodeClimateReport bool
	SendBitBucketInsights     bool
	QodanaYamlConfig          QodanaYamlConfig
}

func (b ContextBuilder) Build() Context {
	return Context{
		linterInfo:                b.LinterInfo,
		mountInfo:                 b.MountInfo,
		cloudData:                 b.CloudData,
		projectDir:                b.ProjectDir,
		resultsDir:                b.ResultsDir,
		reportDir:                 b.ReportDir,
		logDir:                    b.LogDir,
		cacheDir:                  b.CacheDir,
		clangCompileCommands:      b.ClangCompileCommands,
		clangArgs:                 b.ClangArgs,
		property:                  b.Property,
		cdnetSolution:             b.CdnetSolution,
		cdnetProject:              b.CdnetProject,
		cdnetConfiguration:        b.CdnetConfiguration,
		cdnetPlatform:             b.CdnetPlatform,
		noStatistics:              b.NoStatistics,
		cdnetNoBuild:              b.CdnetNoBuild,
		analysisId:                b.AnalysisId,
		baseline:                  b.Baseline,
		baselineIncludeAbsent:     b.BaselineIncludeAbsent,
		generateCodeClimateReport: b.GenerateCodeClimateReport,
		sendBitBucketInsights:     b.SendBitBucketInsights,
		failThreshold:             b.FailThreshold,
		qodanaYamlConfig:          b.QodanaYamlConfig,
	}
}

func (c Context) LinterInfo() LinterInfo                { return c.linterInfo }
func (c Context) MountInfo() MountInfo                  { return c.mountInfo }
func (c Context) CloudData() ThirdPartyStartupCloudData { return c.cloudData }
func (c Context) ProjectDir() string                    { return c.projectDir }
func (c Context) ResultsDir() string                    { return c.resultsDir }
func (c Context) ReportDir() string                     { return c.reportDir }
func (c Context) LogDir() string                        { return c.logDir }
func (c Context) CacheDir() string                      { return c.cacheDir }
func (c Context) ClangCompileCommands() string          { return c.clangCompileCommands }
func (c Context) ClangArgs() string                     { return c.clangArgs }
func (c Context) CdnetSolution() string                 { return c.cdnetSolution }
func (c Context) CdnetProject() string                  { return c.cdnetProject }
func (c Context) CdnetConfiguration() string            { return c.cdnetConfiguration }
func (c Context) CdnetPlatform() string                 { return c.cdnetPlatform }
func (c Context) NoStatistics() bool                    { return c.noStatistics }
func (c Context) CdnetNoBuild() bool                    { return c.cdnetNoBuild }
func (c Context) AnalysisId() string                    { return c.analysisId }
func (c Context) Baseline() string                      { return c.baseline }
func (c Context) BaselineIncludeAbsent() bool           { return c.baselineIncludeAbsent }
func (c Context) FailThreshold() string                 { return c.failThreshold }
func (c Context) GenerateCodeClimateReport() bool       { return c.generateCodeClimateReport }
func (c Context) SendBitBucketInsights() bool           { return c.sendBitBucketInsights }
func (c Context) QodanaYamlConfig() QodanaYamlConfig    { return c.qodanaYamlConfig }

func (c Context) Property() []string {
	props := make([]string, len(c.property))
	copy(props, c.property)
	return props
}

func (c Context) IsCommunity() bool {
	return c.CloudData().LicensePlan == "COMMUNITY"
}

func (c Context) ClangPath() string {
	return c.MountInfo().CustomTools[Clang]
}

func (i LinterInfo) GetMajorVersion() string {
	re := regexp.MustCompile(`\b\d+\.\d+`)
	matches := re.FindStringSubmatch(i.LinterVersion)
	if len(matches) == 0 {
		return product.ReleaseVersion
	}
	return matches[0]
}
