package debug

import (
	"context"
	"fmt"

	"github.com/pingcap-inc/alioth/pkg/util"
	logpattern_go_proto "github.com/pingcap-inc/alioth/proto"
	"github.com/pingcap-inc/alioth/recorder"
	"github.com/pingcap-inc/alioth/storage"
	"github.com/pingcap/log"
	"github.com/spf13/cobra"
	"go.uber.org/zap"
)

var (
	SourcePath    string
	TargetPath    string
	SourceVersion string
	TargetVersion string
)

func NewDiffCmd() *cobra.Command {
	cmdDisplay := &cobra.Command{
		Use:   "diff",
		Short: "diff pattern/coverage/unknown log from two stores",
		//Args:  cobra.MinimumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			Diff(SourcePath, TargetPath)
			return nil
		},
	}

	cmdDisplay.Flags().StringVar(&SourcePath, "A", "", "the A store path of log pattern/coverage/unknown logs used to diff")
	cmdDisplay.Flags().StringVar(&TargetPath, "B", "", "the B store path of log pattern/coverage/unknown logs used to diff")
	cmdDisplay.Flags().StringVar(&SourceVersion, "AV", "", "the A version of log pattern/coverage logs used to diff")
	cmdDisplay.Flags().StringVar(&TargetVersion, "BV", "", "the B version of log pattern/coverage logs used to diff")
	cmdDisplay.Flags().StringVar(&ObjectName, "object", "pattern", "display which object.(pattern, coverage, unknown)")
	cmdDisplay.MarkFlagRequired("A")
	cmdDisplay.MarkFlagRequired("B")
	return cmdDisplay
}

func Diff(source, target string) error {
	sourceStore, err := storage.NewStore(source)
	if err != nil {
		log.Fatal("access log pattern store failed", zap.String("path", source), zap.Error(err))
	}

	var targetStore *storage.LogPatternStore
	if target == source {
		targetStore = sourceStore
	} else {
		targetStore, err = storage.NewStore(target)
		if err != nil {
			log.Fatal("access log pattern store failed", zap.String("path", target), zap.Error(err))
		}
	}

	sourceSet := NewLogSet()
	targetSet := NewLogSet()

	if ObjectName == "unknown" {
		sourceUnknowLogs := recorder.NewUnknowLogRecorder(sourceStore)
		err = sourceUnknowLogs.LoadUnknowLogs(context.Background())
		if err != nil {
			log.Fatal("load unknow logs failed", zap.String("path", source), zap.Error(err))
		}

		sourceUnknowLogs.Forach(context.Background(), func(ctx context.Context, record *logpattern_go_proto.UnknowLogPattern) error {
			ld := UnKnowLogToDetail(record)
			sourceSet.AddLog(record.GetPos().FilePath, ld)
			return nil
		})

		targetUnknowLogs := recorder.NewUnknowLogRecorder(targetStore)
		err = targetUnknowLogs.LoadUnknowLogs(context.Background())
		if err != nil {
			log.Fatal("load unknow logs failed", zap.String("path", target), zap.Error(err))
		}

		targetUnknowLogs.Forach(context.Background(), func(ctx context.Context, record *logpattern_go_proto.UnknowLogPattern) error {
			ld := UnKnowLogToDetail(record)
			targetSet.AddLog(record.GetPos().FilePath, ld)
			return nil
		})
	} else if ObjectName == "coverage" {
		sc := recorder.NewCoverager(sourceStore)
		err = sc.LoadCoverage(context.Background())
		if err != nil {
			log.Fatal("load coverage data failed", zap.String("path", source), zap.Error(err))
		}

		sc.Forach(context.Background(), func(ctx context.Context, record *logpattern_go_proto.Coverage) error {
			if util.CompareVersion(SourceVersion, record.Pos) {
				ld := CoverageToDetail(record)
				sourceSet.AddLog(record.GetPos().FilePath, ld)
			}
			return nil
		})

		tc := recorder.NewCoverager(targetStore)
		err = tc.LoadCoverage(context.Background())
		if err != nil {
			log.Fatal("load coverage data failed", zap.String("path", target), zap.Error(err))
		}

		tc.Forach(context.Background(), func(ctx context.Context, record *logpattern_go_proto.Coverage) error {
			if util.CompareVersion(TargetVersion, record.Pos) {
				ld := CoverageToDetail(record)
				targetSet.AddLog(record.GetPos().FilePath, ld)
			}
			return nil
		})
	} else if ObjectName == "pattern" {
		sp := recorder.NewLogPattern(sourceStore)
		err = sp.LoadPattern(context.Background())
		if err != nil {
			log.Fatal("load coverage data failed", zap.String("path", source), zap.Error(err))
		}

		sp.Forach(context.Background(), func(ctx context.Context, record *logpattern_go_proto.LogPattern) error {
			if util.CompareVersion(SourceVersion, record.Pos) {
				ld := PatternToDetail(record, nil)
				sourceSet.AddLog(record.GetPos().FilePath, ld)
			}
			return nil
		})
		tp := recorder.NewLogPattern(targetStore)
		err = tp.LoadPattern(context.Background())
		if err != nil {
			log.Fatal("load coverage data failed", zap.String("path", source), zap.Error(err))
		}

		tp.Forach(context.Background(), func(ctx context.Context, record *logpattern_go_proto.LogPattern) error {
			if util.CompareVersion(TargetVersion, record.Pos) {
				ld := PatternToDetail(record, nil)
				targetSet.AddLog(record.GetPos().FilePath, ld)
			}
			return nil
		})
	} else {
		log.Fatal("try to ./logcov debug display --object=pattern")
	}

	DiffLogSet(sourceSet, targetSet)

	return nil
}

func DiffLogSet(a, b *LogSet) {
	if a.Total != b.Total {
		fmt.Printf("total count - A: %d and B: %d\n", a.Total, b.Total)
	}

	for path, ai := range a.index {
		if _, ok := b.index[path]; !ok {
			fmt.Printf("A has more file\n %s\n", a.Details[ai])
		}
	}

	for path, bi := range b.index {
		if _, ok := a.index[path]; !ok {
			fmt.Printf("A lack file ile\n %s\n", b.Details[bi])
		}
	}

	for path, ai := range a.index {
		if bi, ok := b.index[path]; ok {
			DiffFileDetail(a.Details[ai], b.Details[bi])
		}
	}
}

func DiffFileDetail(a, b *FileDetail) {
	str := ""

	if a.Count != b.Count {
		str += fmt.Sprintf("Count %d and %d\n", a.Count, b.Count)
	}

	for pos, ai := range a.index {
		if _, ok := b.index[pos]; !ok {
			str += fmt.Sprintf("more log: %s\n", a.Details[ai])
		}
	}

	for pos, bi := range b.index {
		if _, ok := a.index[pos]; !ok {
			str += fmt.Sprintf("lack log: %s\n", b.Details[bi])
		}
	}

	for pos, ai := range a.index {
		if bi, ok := b.index[pos]; ok {
			str += DiffLogDetail(a.Details[ai], b.Details[bi])
		}
	}

	if len(str) > 0 {
		fmt.Printf("File %s\n%s\n", a.Path, str)

	}
}

func DiffLogDetail(a, b *LogDetail) string {
	str := ""
	if a.Count != b.Count {
		str += fmt.Sprintf("log Count - %s: %d and %s: %d\n", a.Position, a.Count, b.Position, b.Count)
	}
	return str
}
