package bundler

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"os"
	"strings"

	"google.golang.org/protobuf/proto"

	"github.com/sqlc-dev/sqlc/internal/config"
	"github.com/sqlc-dev/sqlc/internal/info"
	"github.com/sqlc-dev/sqlc/internal/plugin"
	"github.com/sqlc-dev/sqlc/internal/quickdb"
	pb "github.com/sqlc-dev/sqlc/internal/quickdb/v1"
)

var ErrNoProject = errors.New(`project uploads require a cloud project

If you don't have a project, you can create one from the sqlc Cloud
dashboard at https://dashboard.sqlc.dev/. If you have a project, ensure
you've set its id as the value of the "project" field within the "cloud"
section of your sqlc configuration. The id will look similar to
"01HA8TWGMYPHK0V2GGMB3R2TP9".`)
var ErrNoAuthToken = errors.New(`project uploads require an auth token

If you don't have an auth token, you can create one from the sqlc Cloud
dashboard at https://dashboard.sqlc.dev/. If you have an auth token, ensure
you've set it as the value of the SQLC_AUTH_TOKEN environment variable.`)

type Uploader struct {
	configPath string
	config     *config.Config
	dir        string
	client     pb.QuickClient
}

type QuerySetArchive struct {
	Name    string
	Queries []string
	Schema  []string
	Request *plugin.GenerateRequest
}

func NewUploader(configPath, dir string, conf *config.Config) *Uploader {
	return &Uploader{
		configPath: configPath,
		config:     conf,
		dir:        dir,
	}
}

func (up *Uploader) Validate() error {
	if up.config.Cloud.Project == "" {
		return ErrNoProject
	}
	if up.config.Cloud.AuthToken == "" {
		return ErrNoAuthToken
	}
	if up.client == nil {
		client, err := quickdb.NewClientFromConfig(up.config.Cloud)
		if err != nil {
			return fmt.Errorf("client init failed: %w", err)
		}
		up.client = client
	}
	return nil
}

var envvars = []string{
	"GITHUB_REPOSITORY",
	"GITHUB_REF",
	"GITHUB_REF_NAME",
	"GITHUB_REF_TYPE",
	"GITHUB_SHA",
}

func annotate() map[string]string {
	labels := map[string]string{}
	for _, ev := range envvars {
		key := strings.ReplaceAll(strings.ToLower(ev), "_", ".")
		labels[key] = os.Getenv(ev)
	}
	return labels
}

func BuildRequest(ctx context.Context, dir, configPath string, results []*QuerySetArchive) (*pb.UploadArchiveRequest, error) {
	conf, err := readFile(dir, configPath)
	if err != nil {
		return nil, err
	}
	res := &pb.UploadArchiveRequest{
		SqlcVersion: info.Version,
		Config:      conf,
		Annotations: annotate(),
	}
	for i, result := range results {
		schema, err := readFiles(dir, result.Schema)
		if err != nil {
			return nil, err
		}
		queries, err := readFiles(dir, result.Queries)
		if err != nil {
			return nil, err
		}
		name := result.Name
		if name == "" {
			name = fmt.Sprintf("queryset_%d", i)
		}
		genreq, err := proto.Marshal(result.Request)
		if err != nil {
			return nil, err
		}
		res.QuerySets = append(res.QuerySets, &pb.QuerySet{
			Name:    name,
			Schema:  schema,
			Queries: queries,
			CodegenRequest: &pb.File{
				Name:     "codegen_request.pb",
				Contents: genreq,
			},
		})
	}
	return res, nil
}

func (up *Uploader) buildRequest(ctx context.Context, results []*QuerySetArchive) (*pb.UploadArchiveRequest, error) {
	return BuildRequest(ctx, up.dir, up.configPath, results)
}

func (up *Uploader) DumpRequestOut(ctx context.Context, result []*QuerySetArchive) error {
	req, err := up.buildRequest(ctx, result)
	if err != nil {
		return err
	}
	slog.Info("config", "file", req.Config.Name, "bytes", len(req.Config.Contents))
	for _, qs := range req.QuerySets {
		slog.Info("codegen_request", "queryset", qs.Name, "file", "codegen_request.pb")
		for _, file := range qs.Schema {
			slog.Info("schema", "queryset", qs.Name, "file", file.Name, "bytes", len(file.Contents))
		}
		for _, file := range qs.Queries {
			slog.Info("query", "queryset", qs.Name, "file", file.Name, "bytes", len(file.Contents))
		}
	}
	return nil
}

func (up *Uploader) Upload(ctx context.Context, result []*QuerySetArchive) error {
	if err := up.Validate(); err != nil {
		return err
	}
	req, err := up.buildRequest(ctx, result)
	if err != nil {
		return err
	}
	if _, err := up.client.UploadArchive(ctx, req); err != nil {
		return fmt.Errorf("upload error: %w", err)
	}
	return nil
}
