package release

import (
	"context"
	"errors"
	"fmt"

	strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"

	"github.com/APIParkLab/APIPark/service/strategy"

	api_doc "github.com/APIParkLab/APIPark/service/api-doc"
	service_doc "github.com/APIParkLab/APIPark/service/service-doc"

	"github.com/APIParkLab/APIPark/service/cluster"
	"github.com/APIParkLab/APIPark/service/service"
	"github.com/APIParkLab/APIPark/service/service_diff"

	"github.com/APIParkLab/APIPark/module/release/dto"
	serviceDiff "github.com/APIParkLab/APIPark/module/service-diff"
	"github.com/APIParkLab/APIPark/service/api"
	"github.com/APIParkLab/APIPark/service/publish"
	"github.com/APIParkLab/APIPark/service/release"
	"github.com/APIParkLab/APIPark/service/universally/commit"
	"github.com/APIParkLab/APIPark/service/upstream"
	"github.com/eolinker/go-common/auto"
	"github.com/eolinker/go-common/store"
	"github.com/eolinker/go-common/utils"
	"gorm.io/gorm"
)

var (
	_                     IReleaseModule = (*imlReleaseModule)(nil)
	projectRuleMustServer                = map[string]bool{
		"as_server": true,
	}
)

type imlReleaseModule struct {
	projectDiffModule serviceDiff.IServiceDiffModule `autowired:""`
	releaseService    release.IReleaseService        `autowired:""`
	apiService        api.IAPIService                `autowired:""`
	apiDocService     api_doc.IAPIDocService         `autowired:""`
	serviceDocService service_doc.IDocService        `autowired:""`
	upstreamService   upstream.IUpstreamService      `autowired:""`
	publishService    publish.IPublishService        `autowired:""`
	strategyService   strategy.IStrategyService      `autowired:""`
	transaction       store.ITransaction             `autowired:""`
	projectService    service.IServiceService        `autowired:""`
	clusterService    cluster.IClusterService        `autowired:""`
}

func (m *imlReleaseModule) latestStrategyCommits(ctx context.Context, serviceId string) ([]*commit.Commit[strategy.Commit], error) {
	list, err := m.strategyService.All(ctx, 2, serviceId)
	if err != nil {
		return nil, fmt.Errorf("get latest strategy failed:%w", err)
	}
	for _, s := range list {
		err = m.strategyService.CommitStrategy(ctx, strategy_dto.ScopeService, serviceId, s.Id, s)
		if err != nil {
			return nil, err
		}
	}
	return m.strategyService.ListLatestStrategyCommit(ctx, strategy_dto.ScopeService, serviceId)
}

func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *dto.CreateInput) (string, error) {
	proInfo, err := m.projectService.Check(ctx, serviceId, projectRuleMustServer)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return "", errors.New("project not found")
		}
		return "", err
	}
	clusters, err := m.clusterService.List(ctx)
	if err != nil || len(clusters) == 0 {
		return "", fmt.Errorf("cluster not set:%w", err)
	}

	apis, err := m.apiService.ListForService(ctx, proInfo.Id)
	if err != nil {
		return "", err
	}
	if len(apis) == 0 {
		return "", errors.New("api not found")
	}
	apiUUIDS := utils.SliceToSlice(apis, func(a *api.API) string {
		return a.UUID
	})
	apiProxy, err := m.apiService.ListLatestCommitProxy(ctx, apiUUIDS...)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return "", errors.New("api  config or  document not found")
		}
		return "", err
	}
	if len(apis) != len(apiProxy) {
		return "", errors.New("api or document not found")
	}

	upstreams, err := m.upstreamService.ListLatestCommit(ctx, cluster.DefaultClusterID, serviceId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return "", errors.New("api  config or  document not found")
		}
		return "", err
	}

	apiProxyCommits := utils.SliceToMapO(apiProxy, func(c *commit.Commit[api.Proxy]) (string, string) {
		return c.Target, c.UUID
	})

	upstreamCommits := utils.SliceToMapArray(upstreams, func(c *commit.Commit[upstream.Config]) string {
		return c.Target
	})
	upstreamCommitsForUKC := utils.MapChange(upstreamCommits, func(ls []*commit.Commit[upstream.Config]) map[string]string {
		return utils.SliceToMapO(ls, func(c *commit.Commit[upstream.Config]) (string, string) {
			return c.Key, c.UUID
		})
	})
	apiInfos, err := m.apiService.ListInfo(ctx, apiUUIDS...)
	var newRelease *release.Release
	err = m.transaction.Transaction(ctx, func(ctx context.Context) error {
		for _, a := range apiInfos {
			err = m.apiService.SaveRequest(ctx, a.UUID, &api.Request{
				Path:      a.Path,
				Methods:   a.Methods,
				Protocols: a.Protocols,
				Match:     a.Match,
				Disable:   a.Disable,
				Upstream:  a.Upstream,
			})
			if err != nil {
				return err
			}
		}
		requestCommits, err := m.apiService.ListLatestCommitRequest(ctx, apiUUIDS...)
		if err != nil {
			return err
		}

		doc, err := m.apiDocService.GetDoc(ctx, serviceId)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fmt.Errorf("api doc not found")
			}
			return err
		}
		err = m.apiDocService.CommitDoc(ctx, serviceId, doc)
		if err != nil {
			return err
		}
		docCommit, err := m.apiDocService.LatestDocCommit(ctx, serviceId)
		if err != nil {
			return err
		}
		serviceDoc, err := m.serviceDocService.Get(ctx, serviceId)
		if err != nil {
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
			serviceDoc = &service_doc.Doc{
				Doc: "",
			}
		}
		err = m.serviceDocService.CommitDoc(ctx, serviceId, serviceDoc)
		if err != nil {
			return err
		}
		serviceDocCommit, err := m.serviceDocService.LatestDocCommit(ctx, serviceId)
		if err != nil {
			return err
		}

		strategies, err := m.latestStrategyCommits(ctx, serviceId)
		if err != nil {
			return err
		}
		strategyCommits := utils.SliceToMapO(strategies, func(c *commit.Commit[strategy.Commit]) (string, string) {
			return c.Target, c.UUID
		})

		if !m.releaseService.Completeness(utils.SliceToSlice(clusters, func(s *cluster.Cluster) string {
			return s.Uuid
		}), apiUUIDS, requestCommits, apiProxy, upstreams) {
			return errors.New("completeness check failed")
		}
		requestCommitMap := utils.SliceToMapO(requestCommits, func(c *commit.Commit[api.Request]) (string, string) {
			return c.Target, c.UUID
		})
		newRelease, err = m.releaseService.CreateRelease(ctx, serviceId, input.Version, input.Remark, requestCommitMap, apiProxyCommits, docCommit.UUID, serviceDocCommit.UUID, upstreamCommitsForUKC, strategyCommits)
		return err
	})
	if err != nil {
		return "", err
	}

	return newRelease.UUID, err
}

func (m *imlReleaseModule) Detail(ctx context.Context, project string, id string) (*dto.Detail, error) {
	r, err := m.releaseService.GetRelease(ctx, id)
	if err != nil {
		return nil, err
	}
	if r.Service != project {
		return nil, errors.New("release not found")
	}
	running, err := m.releaseService.GetRunning(ctx, project)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}
	runningRelease := ""
	if running != nil {
		runningRelease = running.UUID
	}
	diff, err := m.projectDiffModule.Diff(ctx, project, runningRelease, r.UUID)
	if err != nil {
		return nil, err
	}
	out, err := m.projectDiffModule.Out(ctx, diff)
	if err != nil {
		return nil, err
	}
	return &dto.Detail{
		Id:         r.UUID,
		Version:    r.Version,
		Remark:     r.Remark,
		Service:    auto.UUID(r.Service),
		CreateTime: auto.TimeLabel(r.CreateAt),
		Creator:    auto.UUID(r.Creator),
		Diffs:      out,
	}, nil
}

func (m *imlReleaseModule) List(ctx context.Context, project string) ([]*dto.Release, error) {
	_, err := m.projectService.Check(ctx, project, projectRuleMustServer)
	if err != nil {
		return nil, err
	}
	list, err := m.releaseService.List(ctx, project)
	if err != nil {
		return nil, err
	}
	running, err := m.releaseService.GetRunning(ctx, project)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}

	releaseIds := utils.SliceToSlice(list, func(s *release.Release) string {
		return s.UUID
	})
	flows, err := m.publishService.Latest(ctx, releaseIds...)
	if err != nil {
		return nil, err
	}
	flowMap := utils.SliceToMap(flows, func(s *publish.Publish) string {
		return s.Release
	})

	return utils.SliceToSlice(list, func(s *release.Release) *dto.Release {

		r := &dto.Release{
			Id:          s.UUID,
			Service:     auto.UUID(s.Service),
			Version:     s.Version,
			Remark:      s.Remark,
			Status:      dto.StatusNone,
			CanRollback: false,
			CanDelete:   true,
			Creator:     auto.UUID(s.Creator),
			CreateTime:  auto.TimeLabel(s.CreateAt),
		}

		if running != nil && running.UUID == s.UUID {
			r.Status = dto.StatusRunning
			r.CanRollback = true
			r.CanDelete = false
		}
		flow, has := flowMap[s.UUID]
		if has {
			r.FlowId = flow.Id

			if flow.Status == publish.StatusApply {
				r.Status = dto.StatusApply
				r.CanDelete = false
			} else if flow.Status == publish.StatusAccept {

				r.Status = dto.StatusAccept
				r.CanDelete = false
			} else if flow.Status == publish.StatusPublishError {
				r.Status = dto.StatusError
				r.CanDelete = false
			}
		}
		return r
	}), nil
}

func (m *imlReleaseModule) Delete(ctx context.Context, project string, id string) error {
	_, err := m.projectService.Check(ctx, project, projectRuleMustServer)
	if err != nil {
		return err
	}
	return m.transaction.Transaction(ctx, func(ctx context.Context) error {
		r, err := m.releaseService.GetRelease(ctx, id)
		if err != nil {
			return err
		}
		if r == nil {
			return errors.New("release not found")
		}
		if r.Service != project {
			return errors.New("project not match")
		}
		running, err := m.releaseService.GetRunning(ctx, project)
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		if running != nil && running.UUID == id {
			return errors.New("can not delete running release")
		}
		flow, err := m.publishService.GetLatest(ctx, id)
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		if flow != nil {
			if flow.Status == publish.StatusApply || flow.Status == publish.StatusAccept {
				return errors.New("can not delete  release in apply or approve flow")
			}
		}
		return m.releaseService.DeleteRelease(ctx, id)
	})

}

func (m *imlReleaseModule) Preview(ctx context.Context, project string) (*dto.Release, *service_diff.Diff, bool, error) {
	_, err := m.projectService.Check(ctx, project, projectRuleMustServer)
	if err != nil {
		return nil, nil, false, err
	}
	running, err := m.releaseService.GetRunning(ctx, project)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil, false, err
	}

	if running == nil {
		running = new(release.Release)
	}

	diff, completeness, err := m.projectDiffModule.DiffForLatest(ctx, project, running.UUID)
	if err != nil {
		return nil, nil, false, err
	}
	return &dto.Release{
		Id:          running.UUID,
		Version:     running.Version,
		Service:     auto.UUID(project),
		CreateTime:  auto.TimeLabel(running.CreateAt),
		Creator:     auto.UUID(running.Creator),
		Status:      dto.StatusNone,
		Remark:      running.Remark,
		CanDelete:   false,
		CanRollback: false,
	}, diff, completeness, nil

}
