package providerproxy

import (
	"fmt"

	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"

	"github.com/aws/eks-anywhere-test-tool/pkg/cloudwatch"
	"github.com/aws/eks-anywhere-test-tool/pkg/codebuild"
	"github.com/aws/eks-anywhere-test-tool/pkg/constants"
	"github.com/aws/eks-anywhere-test-tool/pkg/fileutils"
	"github.com/aws/eks-anywhere/pkg/logger"
)

type FetchSessionOpts func(options *fetchSessionsConfig) (err error)

func WithCodebuildBuild(buildId string) FetchSessionOpts {
	return func(options *fetchSessionsConfig) (err error) {
		options.buildId = buildId
		logger.Info("user provided build ID detected", "buildId", buildId)
		return err
	}
}

func WithCodebuildProject(project string) FetchSessionOpts {
	return func(options *fetchSessionsConfig) (err error) {
		options.project = project
		logger.Info("user provided project ID detected", "project", project)
		return err
	}
}

type fetchSessionsConfig struct {
	buildId string
	project string
}

type (
	requestFilter   func(logs []*cloudwatchlogs.OutputLogEvent) (filteredLogs []*cloudwatchlogs.OutputLogEvent, err error)
	requestConsumer func(logs []*cloudwatchlogs.OutputLogEvent) error
)

type ProxyFetcherOpt func(*proxyLogFetcher)

func WithLogStdout() ProxyFetcherOpt {
	return func(l *proxyLogFetcher) {
		l.processRequests = func(logs []*cloudwatchlogs.OutputLogEvent) error { return nil }
	}
}

type proxyLogFetcher struct {
	buildAccountCwClient        *cloudwatch.Cloudwatch
	testAccountCwClient         *cloudwatch.Cloudwatch
	buildAccountCodebuildClient *codebuild.Codebuild
	writer                      *requestWriter
	filterRequests              requestFilter
	processRequests             requestConsumer
}

func New(buildAccountCwClient *cloudwatch.Cloudwatch, testAccountCwClient *cloudwatch.Cloudwatch, buildAccountCodebuildClient *codebuild.Codebuild, opts ...ProxyFetcherOpt) *proxyLogFetcher {
	l := &proxyLogFetcher{
		buildAccountCwClient:        buildAccountCwClient,
		testAccountCwClient:         testAccountCwClient,
		buildAccountCodebuildClient: buildAccountCodebuildClient,
	}
	for _, o := range opts {
		o(l)
	}

	defaultOutputFolder := fileutils.GenOutputDirName("provider-proxy-logs")

	if l.filterRequests == nil {
		l.filterRequests = noFilter
	}

	if l.processRequests == nil {
		_ = l.ensureWriter(defaultOutputFolder)
		l.processRequests = l.writer.writeRequest
	}

	return l
}

func (l *proxyLogFetcher) FetchProviderProxyLogs(opts ...FetchSessionOpts) error {
	config := &fetchSessionsConfig{
		project: constants.EksATestCodebuildProject,
	}

	for _, opt := range opts {
		err := opt(config)
		if err != nil {
			return fmt.Errorf("failed to set options on fetch logs config: %v", err)
		}
	}

	if config.buildId == "" {
		p, err := l.buildAccountCodebuildClient.FetchLatestBuildForProject(config.project)
		if err != nil {
			return fmt.Errorf("failed to get latest build for project: %v", err)
		}
		config.buildId = *p.Id
		logger.Info("Using latest build for selected project", "buildID", config.buildId, "project", config.project)
	}

	logs, err := l.FetchProviderProxyLogsForbuild(config.project, config.buildId)
	if err != nil {
		return err
	}
	err = l.processRequests(logs)
	if err != nil {
		return err
	}
	return nil
}

func (l *proxyLogFetcher) FetchProviderProxyLogsForbuild(project string, buildId string) ([]*cloudwatchlogs.OutputLogEvent, error) {
	logger.Info("Fetching provider proxy logs...")
	build, err := l.buildAccountCodebuildClient.FetchBuildForProject(buildId)
	if err != nil {
		return nil, fmt.Errorf("fetching build for project %s: %v", project, err)
	}

	buildStart := build.StartTime.UnixNano() / 1e6
	logger.Info("Starting log time", "Start time", buildStart)

	buildEnd := build.EndTime.UnixNano() / 1e6
	logger.Info("Ending log time", "Start time", buildEnd)

	logs, err := l.buildAccountCwClient.GetLogsInTimeframe(constants.CiProxyLogGroup, constants.CiProxyLogStream, buildStart, buildEnd)
	if err != nil {
		return nil, fmt.Errorf("fetching cloudwatch logs: %v", err)
	}
	filteredLogs, err := l.filterRequests(logs)
	return filteredLogs, err
}

func (l *proxyLogFetcher) ensureWriter(folderPath string) error {
	if l.writer != nil {
		return nil
	}

	var err error
	l.writer, err = newRequestWriter(folderPath)
	if err != nil {
		return err
	}

	return nil
}

func noFilter(logs []*cloudwatchlogs.OutputLogEvent) (outputLogs []*cloudwatchlogs.OutputLogEvent, err error) {
	return logs, nil
}
