package logging

import (
	"fmt"
	"io"
	"log"
	"os"

	"github.com/UpmeshLTD/urus-aio/infrastructure/logging/cloudwatchwriter"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/rs/zerolog"
)

type zerologLogger struct {
	zerolog *zerolog.Logger
}

// NewZerologLogger returns a new instance of Logger
func NewZerologLogger(cfg LoggerConfig) Logger {
	var cWriter io.Writer

	// set global config
	zerolog.CallerSkipFrameCount = 3
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix

	if v, err := zerolog.ParseLevel(cfg.Level); err == nil {
		zerolog.SetGlobalLevel(v)
	}

	log.Println("logger level:", zerolog.GlobalLevel())

	if cfg.CloudWatchLog != nil {
		func() {
			log.Println("initialize CloudWatchWriter")

			if cfg.CloudWatchLog.Region == "" || cfg.CloudWatchLog.GroupName == "" || cfg.CloudWatchLog.StreamName == "" {
				log.Println("failed to initialize CloudWatch: missing configs Region, GroupName or StreamName")
				return
			}

			// init cloudwatch
			sess, err := session.NewSession(&aws.Config{
				Region: aws.String(cfg.CloudWatchLog.Region),
			})
			if err != nil {
				log.Println("failed to create a session:", err)
				return
			}

			// check credentials
			if _, err := sts.New(sess).GetCallerIdentity(&sts.GetCallerIdentityInput{}); err != nil {
				log.Println("failed to validate aws credentials:", err)
				return
			}

			cWriter, err = cloudwatchwriter.New(
				cloudwatchlogs.New(sess),
				cfg.CloudWatchLog.BatchFrequency,
				cfg.CloudWatchLog.GroupName,
				cfg.CloudWatchLog.StreamName,
			)
			if err != nil {
				log.Println("failed to initialize CloudWatchWriter:", err)
				return
			}

			log.Println("successfully initialize CloudWatchWriter")
		}()
	}

	zerologWriter := func() io.Writer {
		// colorized logs in console development mode
		if cfg.Colorized {
			return io.MultiWriter(zerolog.NewConsoleWriter())
		}
		return io.MultiWriter(os.Stdout)
	}()

	if cWriter != nil {
		// set custom log output (append writer to zerolog writer)
		zerologWriter = io.MultiWriter(zerologWriter, cWriter)

		// set std log output
		log.SetOutput(io.MultiWriter(os.Stdout, cWriter))

		// set cloudwatch to logging writer
		writer = &cWriter
	}

	// init logger with options
	logger := zerolog.New(zerologWriter).With().Timestamp().Caller().Logger()
	log.Println("successfully initialize ZerologLogger")

	return &zerologLogger{
		zerolog: &logger,
	}
}

func (l *zerologLogger) Print(args ...interface{}) {
	l.Info(args...)
}

func (l *zerologLogger) Println(args ...interface{}) {
	l.Info(args...)
}

func (l *zerologLogger) Printf(format string, args ...interface{}) {
	l.Infof(format, args...)
}

func (l *zerologLogger) Error(args ...interface{}) {
	l.zerolog.Error().Msg(fmt.Sprint(args...))
}

func (l *zerologLogger) Warn(args ...interface{}) {
	l.zerolog.Warn().Msg(fmt.Sprint(args...))
}

func (l *zerologLogger) Info(args ...interface{}) {
	l.zerolog.Info().Msg(fmt.Sprint(args...))
}

func (l *zerologLogger) Debug(args ...interface{}) {
	l.zerolog.Debug().Msg(fmt.Sprint(args...))
}

func (l *zerologLogger) Errorf(format string, args ...interface{}) {
	l.zerolog.Error().Msgf(format, args...)
}

func (l *zerologLogger) Warnf(format string, args ...interface{}) {
	l.zerolog.Warn().Msgf(format, args...)
}

func (l *zerologLogger) Infof(format string, args ...interface{}) {
	l.zerolog.Info().Msgf(format, args...)
}

func (l *zerologLogger) Debugf(format string, args ...interface{}) {
	l.zerolog.Debug().Msgf(format, args...)
}

func (l *zerologLogger) Errorw(msg string, args ...interface{}) {
	l.zerolog.Error().Fields(args).Msg(msg)
}

func (l *zerologLogger) Warnw(msg string, args ...interface{}) {
	l.zerolog.Warn().Fields(args).Msg(msg)
}

func (l *zerologLogger) Infow(msg string, args ...interface{}) {
	l.zerolog.Info().Fields(args).Msg(msg)
}

func (l *zerologLogger) Debugw(msg string, args ...interface{}) {
	l.zerolog.Debug().Fields(args).Msg(msg)
}

func (l *zerologLogger) With(args ...interface{}) Logger {
	fields := func() map[string]interface{} {
		if len(args) == 1 {
			switch v := args[0].(type) {
			case map[string]interface{}:
				return v
			}
		} else {
			fields, err := keyValuePairs(args...)
			if err == nil {
				return fields
			}
		}
		return map[string]interface{}{
			"fields": args,
		}
	}()
	logger := l.zerolog.With().Fields(fields).Logger()
	return &zerologLogger{
		zerolog: &logger,
	}
}

func (l *zerologLogger) Writer() *io.Writer {
	return nil
}
