/*
 * Copyright (c) Microsoft Corporation.
 * Licensed under the MIT license.
 */

package pipeline

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/rotisserie/eris"
	kerrors "k8s.io/apimachinery/pkg/util/errors"

	"github.com/Azure/azure-service-operator/v2/tools/generator/internal/astmodel"
	"github.com/Azure/azure-service-operator/v2/tools/generator/internal/config"
	"github.com/Azure/azure-service-operator/v2/tools/generator/internal/reporting"
)

// ReportOnTypesAndVersionsStageID is the unique identifier of this stage
const ReportOnTypesAndVersionsStageID = "reportTypesAndVersions"

// ReportOnTypesAndVersions creates a pipeline stage that generates a report for each group showing a matrix of all
// types and versions
func ReportOnTypesAndVersions(configuration *config.Configuration) *Stage {
	return NewStage(
		ReportOnTypesAndVersionsStageID,
		"Generate reports on types and versions in each package",
		func(ctx context.Context, state *State) (*State, error) {
			definitions := state.Definitions()
			report := NewPackagesMatrixReport()
			report.Summarize(definitions)
			err := report.WriteTo(configuration.FullTypesOutputPath())
			return state, err
		})
}

type PackagesMatrixReport struct {
	// A separate table for each package
	tables map[string]*reporting.SparseTable
}

func NewPackagesMatrixReport() *PackagesMatrixReport {
	return &PackagesMatrixReport{
		tables: make(map[string]*reporting.SparseTable),
	}
}

func (report *PackagesMatrixReport) Summarize(definitions astmodel.TypeDefinitionSet) {
	for _, t := range definitions {
		typeName := t.Name().Name()
		packageName := report.ServiceName(t.Name().InternalPackageReference())
		packageVersion := t.Name().PackageReference().PackageName()
		table, ok := report.tables[packageName]
		if !ok {
			table = reporting.NewSparseTable(fmt.Sprintf("Type Definitions in package %q", packageName))
			report.tables[packageName] = table
		}

		table.SetCell(typeName, packageVersion, packageVersion)
	}
}

func (report *PackagesMatrixReport) WriteTo(outputPath string) error {
	var errs []error
	for pkg, table := range report.tables {
		err := report.WriteTableTo(table, pkg, outputPath)
		if err != nil {
			errs = append(errs, err)
		}
	}

	return kerrors.NewAggregate(errs)
}

func (report *PackagesMatrixReport) ServiceName(ref astmodel.InternalPackageReference) string {
	grp, _ := ref.GroupVersion()
	return grp
}

func (report *PackagesMatrixReport) WriteTableTo(table *reporting.SparseTable, pkg string, outputPath string) error {
	table.SortColumns(func(left string, right string) bool {
		return left < right
	})
	table.SortRows(func(top string, bottom string) bool {
		return top < bottom
	})

	var buffer strings.Builder
	table.WriteTo(&buffer)

	outputFolder := filepath.Join(outputPath, pkg)
	if _, err := os.Stat(outputFolder); os.IsNotExist(err) {
		err = os.MkdirAll(outputFolder, 0o700)
		if err != nil {
			return eris.Wrapf(err, "Unable to create directory %q", outputFolder)
		}
	}

	destination := filepath.Join(outputFolder, "versions_matrix.md")
	return os.WriteFile(destination, []byte(buffer.String()), 0o600)
}
