// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package serverpsql

import (
	"fmt"
	"github.com/jmoiron/sqlx/types"
	"github.com/lib/pq"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"
	"peridot.resf.org/peridot/db/models"
	peridotpb "peridot.resf.org/peridot/pb"
	"peridot.resf.org/utils"
)

func (a *Access) ListProjects(filters *peridotpb.ProjectFilters) (ret models.Projects, err error) {
	if filters == nil {
		filters = &peridotpb.ProjectFilters{}
	}

	var ids pq.StringArray = nil
	if filters.Ids != nil {
		ids = filters.Ids
	}

	err = a.query.Select(
		&ret,
		`
		select
			id,
			created_at,
			updated_at,
			name,
			major_version,
			dist_tag_override,
			target_gitlab_host,
			target_prefix,
			target_branch_prefix,
			source_git_host,
			source_prefix,
			source_branch_prefix,
			cdn_url,
			strict_mode,
			target_vendor,
			additional_vendor,
			archs,
			follow_import_dist,
			branch_suffix,
			git_make_public,
			vendor_macro,
			packager_macro
		from projects
		where
			($1 :: uuid is null or id = $1 :: uuid)
			and ($2 :: text is null or name ~* $2 :: text)
			and ($3 :: uuid[] is null or id = any($3 :: uuid[]))
		order by created_at desc
		`,
		utils.StringValueP(filters.Id),
		utils.StringValueP(filters.Name),
		ids,
	)

	return ret, err
}

func (a *Access) GetProjectKeys(projectId string) (*models.ProjectKey, error) {
	var ret models.ProjectKey
	err := a.query.Get(
		&ret,
		`
		select
			id,
			created_at,
			project_id,
			gitlab_username,
			gitlab_secret
		from project_keys
		where project_id = $1
		`,
		projectId,
	)
	if err != nil {
		return nil, err
	}

	return &ret, nil
}

// GetProjectModuleConfiguration returns the module configurations for the given project.
func (a *Access) GetProjectModuleConfiguration(projectId string) (*peridotpb.ModuleConfiguration, error) {
	var ret types.JSONText
	err := a.query.Get(
		&ret,
		`
        select
            proto
        from project_module_configuration
        where
			project_id = $1
			and active = true
        `,
		projectId,
	)
	if err != nil {
		return nil, err
	}

	anyPb := &anypb.Any{}
	err = protojson.Unmarshal(ret, anyPb)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal module configuration (protojson): %v", err)
	}

	pb := &peridotpb.ModuleConfiguration{}
	err = anypb.UnmarshalTo(anyPb, pb, proto.UnmarshalOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal module configuration: %v", err)
	}

	return pb, nil
}

func (a *Access) CreateProject(project *peridotpb.Project) (*models.Project, error) {
	if err := project.ValidateAll(); err != nil {
		return nil, err
	}

	ret := models.Project{
		Name:               project.Name.Value,
		MajorVersion:       int(project.MajorVersion.Value),
		DistTagOverride:    utils.StringValueToNullString(project.DistTag),
		TargetGitlabHost:   project.TargetGitlabHost.Value,
		TargetPrefix:       project.TargetPrefix.Value,
		TargetBranchPrefix: project.TargetBranchPrefix.Value,
		SourceGitHost:      utils.StringValueToNullString(project.SourceGitHost),
		SourcePrefix:       utils.StringValueToNullString(project.SourcePrefix),
		SourceBranchPrefix: utils.StringValueToNullString(project.SourceBranchPrefix),
		CdnUrl:             utils.StringValueToNullString(project.CdnUrl),
		StreamMode:         project.StreamMode,
		TargetVendor:       project.TargetVendor,
		AdditionalVendor:   project.AdditionalVendor.Value,
		Archs:              project.Archs,
		FollowImportDist:   project.FollowImportDist,
		BranchSuffix:       utils.StringValueToNullString(project.BranchSuffix),
		GitMakePublic:      project.GitMakePublic,
		VendorMacro:        utils.StringValueToNullString(project.VendorMacro),
		PackagerMacro:      utils.StringValueToNullString(project.PackagerMacro),
	}

	err := a.query.Get(
		&ret,
		`
		insert into projects
		(name, major_version, dist_tag_override, target_gitlab_host, target_prefix,
		target_branch_prefix, source_git_host, source_prefix, source_branch_prefix, cdn_url,
		stream_mode, target_vendor, additional_vendor, archs, follow_import_dist, branch_suffix, git_make_public, vendor_macro, packager_macro)
		values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
		returning id, created_at, updated_at
		`,
		ret.Name,
		ret.MajorVersion,
		ret.DistTagOverride,
		ret.TargetGitlabHost,
		ret.TargetPrefix,
		ret.TargetBranchPrefix,
		ret.SourceGitHost,
		ret.SourcePrefix,
		ret.SourceBranchPrefix,
		ret.CdnUrl,
		ret.StreamMode,
		ret.TargetVendor,
		ret.AdditionalVendor,
		ret.Archs,
		ret.FollowImportDist,
		ret.BranchSuffix,
		ret.GitMakePublic,
		ret.VendorMacro,
		ret.PackagerMacro,
	)
	if err != nil {
		return nil, err
	}

	return &ret, nil
}

func (a *Access) UpdateProject(id string, project *peridotpb.Project) (*models.Project, error) {
	if err := project.ValidateAll(); err != nil {
		return nil, err
	}

	ret := models.Project{
		Name:               project.Name.Value,
		MajorVersion:       int(project.MajorVersion.Value),
		DistTagOverride:    utils.StringValueToNullString(project.DistTag),
		TargetGitlabHost:   project.TargetGitlabHost.Value,
		TargetPrefix:       project.TargetPrefix.Value,
		TargetBranchPrefix: project.TargetBranchPrefix.Value,
		SourceGitHost:      utils.StringValueToNullString(project.SourceGitHost),
		SourcePrefix:       utils.StringValueToNullString(project.SourcePrefix),
		SourceBranchPrefix: utils.StringValueToNullString(project.SourceBranchPrefix),
		CdnUrl:             utils.StringValueToNullString(project.CdnUrl),
		StreamMode:         project.StreamMode,
		TargetVendor:       project.TargetVendor,
		AdditionalVendor:   project.AdditionalVendor.Value,
		Archs:              project.Archs,
		FollowImportDist:   project.FollowImportDist,
		BranchSuffix:       utils.StringValueToNullString(project.BranchSuffix),
		GitMakePublic:      project.GitMakePublic,
		VendorMacro:        utils.StringValueToNullString(project.VendorMacro),
		PackagerMacro:      utils.StringValueToNullString(project.PackagerMacro),
	}

	err := a.query.Get(
		&ret,
		`
		update projects set
			name = $1,
			major_version = $2,
			dist_tag_override = $3,
			target_gitlab_host = $4,
			target_prefix = $5,
			target_branch_prefix = $6,
			source_git_host = $7,
			source_prefix = $8,
			source_branch_prefix = $9,
			cdn_url = $10,
			stream_mode = $11,
			target_vendor = $12,
			additional_vendor = $13,
			archs = $14,
			follow_import_dist = $15,
			branch_suffix = $16,
			git_make_public = $17,
            vendor_macro = $18,
            packager_macro = $19,
			updated_at = now()
		where id = $20
		returning id, created_at, updated_at
		`,
		ret.Name,
		ret.MajorVersion,
		ret.DistTagOverride,
		ret.TargetGitlabHost,
		ret.TargetPrefix,
		ret.TargetBranchPrefix,
		ret.SourceGitHost,
		ret.SourcePrefix,
		ret.SourceBranchPrefix,
		ret.CdnUrl,
		ret.StreamMode,
		ret.TargetVendor,
		ret.AdditionalVendor,
		ret.Archs,
		ret.FollowImportDist,
		ret.BranchSuffix,
		ret.GitMakePublic,
		ret.VendorMacro,
		ret.PackagerMacro,
		id,
	)
	if err != nil {
		return nil, err
	}

	return &ret, nil
}

func (a *Access) SetProjectKeys(projectId string, username string, password string) error {
	_, err := a.query.Exec(
		`
		insert into project_keys (project_id, gitlab_username, gitlab_secret)
		values ($1, $2, $3)
		on conflict (project_id) do update
			set gitlab_username = $2, gitlab_secret = $3
		`,
		projectId,
		username,
		password,
	)
	return err
}

func (a *Access) SetBuildRootPackages(projectId string, srpmPackages []string, buildPackages []string) error {
	_, err := a.query.Exec(
		`
		update projects set srpm_stage_packages = $2, build_stage_packages = $3
    where project_id = $1
		`,
		projectId,
		srpmPackages,
		buildPackages,
	)
	return err
}
