// 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 overview

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/spf13/cobra"

	"sigs.k8s.io/yaml"

	"github.com/Azure/ARO-Tools/pipelines/topology"

	"github.com/Azure/ARO-HCP/tooling/pipeline-documentation/pkg/generator"
)

func DefaultGenerationOptions() *RawGenerationOptions {
	return &RawGenerationOptions{}
}

func BindGenerationOptions(opts *RawGenerationOptions, cmd *cobra.Command) error {
	cmd.Flags().StringVar(&opts.Input, "topology", opts.Input, "file holding topology configuration")
	cmd.Flags().StringVar(&opts.Output, "output", opts.Output, "output file path for overview")

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

// RawGenerationOptions holds input values.
type RawGenerationOptions struct {
	Input  string
	Output string
}

// validatedGenerationOptions is a private wrapper that enforces a call of Validate() before Complete() can be invoked.
type validatedGenerationOptions struct {
	*RawGenerationOptions
}

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

// completedGenerationOptions is a private wrapper that enforces a call of Complete() before config generation can be invoked.
type completedGenerationOptions struct {
	Topology   topology.Topology
	OutputFile io.WriteCloser
}

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

func (o *RawGenerationOptions) Validate() (*ValidatedGenerationOptions, error) {
	if o.Input == "" {
		return nil, fmt.Errorf("topology configuration file is required")
	}

	if o.Output == "" {
		return nil, fmt.Errorf("output markdown file is required")
	}

	if _, err := os.Stat(o.Input); errors.Is(err, os.ErrNotExist) {
		return nil, fmt.Errorf("input file %s does not exist", o.Input)
	}

	return &ValidatedGenerationOptions{
		validatedGenerationOptions: &validatedGenerationOptions{
			RawGenerationOptions: o,
		},
	}, nil
}

func (o *ValidatedGenerationOptions) Complete() (*GenerationOptions, error) {
	rawInput, err := os.ReadFile(o.Input)
	if err != nil {
		return nil, fmt.Errorf("failed to read input file %s: %w", o.Input, err)
	}

	var t topology.Topology
	if err := yaml.Unmarshal(rawInput, &t); err != nil {
		return nil, fmt.Errorf("failed to unmarshal topology: %w", err)
	}

	if err := os.MkdirAll(filepath.Dir(o.Output), os.ModePerm); err != nil {
		return nil, fmt.Errorf("failed to create output directory %s: %w", o.Output, err)
	}

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

	return &GenerationOptions{
		completedGenerationOptions: &completedGenerationOptions{
			Topology:   t,
			OutputFile: outputFile,
		},
	}, nil
}

func (opts *GenerationOptions) GenerateOverview() error {
	return generator.Markdown(opts.Topology, opts.OutputFile)
}
