// Copyright 2025 Microsoft Corporation
//
// 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 inspect

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/spf13/cobra"

	"github.com/Azure/ARO-HCP/tooling/templatize/cmd/pipeline/options"
	"github.com/Azure/ARO-HCP/tooling/templatize/pkg/pipeline"
)

func DefaultOptions() *RawInspectOptions {
	return &RawInspectOptions{
		PipelineOptions: options.DefaultOptions(),
	}
}

func BindOptions(opts *RawInspectOptions, cmd *cobra.Command) error {
	err := options.BindOptions(opts.PipelineOptions, cmd)
	if err != nil {
		return fmt.Errorf("failed to bind options: %w", err)
	}
	cmd.Flags().StringVar(&opts.Scope, "scope", opts.Scope, "scope of the pipeline to inspect")
	cmd.Flags().StringVar(&opts.Format, "format", opts.Format, "output format")
	cmd.Flags().StringVar(&opts.Output, "output", opts.Output, "output file")

	if err := cmd.MarkFlagFilename("output"); err != nil {
		return fmt.Errorf("failed to mark flag %q as a file: %w", "output", err)
	}

	return nil
}

type RawInspectOptions struct {
	PipelineOptions *options.RawPipelineOptions
	Scope           string
	Format          string
	Output          string
}

// validatedInspectOptions is a private wrapper that enforces a call of Validate() before Complete() can be invoked.
type validatedInspectOptions struct {
	*RawInspectOptions
	*options.ValidatedPipelineOptions
}

type ValidatedInspectOptions struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*validatedInspectOptions
}

// completedRunOptions is a private wrapper that enforces a call of Complete() before config generation can be invoked.
type completedInspectOptions struct {
	PipelineOptions *options.PipelineOptions
	Scope           string
	Format          string
	OutputFile      io.Writer
}

type InspectOptions struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedInspectOptions
}

func (o *RawInspectOptions) Validate(ctx context.Context) (*ValidatedInspectOptions, error) {
	validatedPipelineOptions, err := o.PipelineOptions.Validate(ctx)
	if err != nil {
		return nil, err
	}

	inspectScopes := pipeline.NewStepInspectScopes(map[string]string{})
	if _, ok := inspectScopes[o.Scope]; !ok {
		scopes := make([]string, 0, len(inspectScopes))
		for scope := range inspectScopes {
			scopes = append(scopes, scope)
		}
		availableScopes := strings.Join(scopes, ", ")
		return nil, fmt.Errorf("unknown inspect scope %q, valid scopes are: (%v)", o.Scope, availableScopes)
	}

	return &ValidatedInspectOptions{
		validatedInspectOptions: &validatedInspectOptions{
			RawInspectOptions:        o,
			ValidatedPipelineOptions: validatedPipelineOptions,
		},
	}, nil
}

func (o *ValidatedInspectOptions) Complete(ctx context.Context) (*InspectOptions, error) {
	completed, err := o.ValidatedPipelineOptions.Complete(ctx)
	if err != nil {
		return nil, err
	}

	outputFile, err := os.Create(o.Output)
	if err != nil {
		return nil, fmt.Errorf("failed to create output file %s: %w", o.Output, err)
	}

	return &InspectOptions{
		completedInspectOptions: &completedInspectOptions{
			PipelineOptions: completed,
			Scope:           o.Scope,
			Format:          o.Format,
			OutputFile:      outputFile,
		},
	}, nil
}

func (o *InspectOptions) RunInspect(ctx context.Context) error {
	return pipeline.Inspect(
		o.PipelineOptions.Pipeline, ctx, &pipeline.InspectOptions{
			Scope:             o.Scope,
			ScopeFunctions:    pipeline.NewStepInspectScopes(o.PipelineOptions.RolloutOptions.Subscriptions),
			Format:            o.Format,
			Step:              o.PipelineOptions.Step,
			Region:            o.PipelineOptions.RolloutOptions.Region,
			Configuration:     o.PipelineOptions.RolloutOptions.Config,
			TopoDirLookupFunc: o.PipelineOptions.TopoDirLookupFunc,
			Service:           o.PipelineOptions.Service,
			OutputFile:        o.OutputFile,
			Concurrency:       o.PipelineOptions.RolloutOptions.Concurrency,
		},
	)
}
