package agent

import (
	"context"
	"fmt"
	"time"

	mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
	"git.wntrmute.dev/mc/mcp/internal/registry"
	"git.wntrmute.dev/mc/mcp/internal/runtime"
	"google.golang.org/protobuf/types/known/timestamppb"
)

// buildServiceInfo converts a registry Service and its components into a proto
// ServiceInfo message.
func buildServiceInfo(svc registry.Service, components []registry.Component) *mcpv1.ServiceInfo {
	info := &mcpv1.ServiceInfo{
		Name:   svc.Name,
		Active: svc.Active,
	}
	for _, c := range components {
		info.Components = append(info.Components, &mcpv1.ComponentInfo{
			Name:          c.Name,
			Image:         c.Image,
			DesiredState:  c.DesiredState,
			ObservedState: c.ObservedState,
			Version:       c.Version,
		})
	}
	return info
}

// ListServices returns all services and their components from the registry.
// It does not query the container runtime.
func (a *Agent) ListServices(ctx context.Context, req *mcpv1.ListServicesRequest) (*mcpv1.ListServicesResponse, error) {
	a.Logger.Debug("ListServices called")

	services, err := registry.ListServices(a.DB)
	if err != nil {
		return nil, fmt.Errorf("list services: %w", err)
	}

	resp := &mcpv1.ListServicesResponse{}
	for _, svc := range services {
		components, err := registry.ListComponents(a.DB, svc.Name)
		if err != nil {
			return nil, fmt.Errorf("list components for %q: %w", svc.Name, err)
		}
		resp.Services = append(resp.Services, buildServiceInfo(svc, components))
	}

	return resp, nil
}

// liveCheckServices queries the container runtime and reconciles with the
// registry. It returns a list of ServiceInfo messages with updated observed
// state. This shared logic is used by both LiveCheck and GetServiceStatus.
func (a *Agent) liveCheckServices(ctx context.Context) ([]*mcpv1.ServiceInfo, error) {
	containers, err := a.Runtime.List(ctx)
	if err != nil {
		return nil, fmt.Errorf("runtime list: %w", err)
	}

	runtimeByName := make(map[string]runtime.ContainerInfo, len(containers))
	for _, c := range containers {
		runtimeByName[c.Name] = c
	}

	matched := make(map[string]bool)

	services, err := registry.ListServices(a.DB)
	if err != nil {
		return nil, fmt.Errorf("list services: %w", err)
	}

	var result []*mcpv1.ServiceInfo
	knownServices := make(map[string]bool, len(services))
	for _, svc := range services {
		knownServices[svc.Name] = true

		components, err := registry.ListComponents(a.DB, svc.Name)
		if err != nil {
			return nil, fmt.Errorf("list components for %q: %w", svc.Name, err)
		}

		info := &mcpv1.ServiceInfo{
			Name:   svc.Name,
			Active: svc.Active,
		}

		for _, comp := range components {
			containerName := ContainerNameFor(svc.Name, comp.Name)
			ci := &mcpv1.ComponentInfo{
				Name:         comp.Name,
				Image:        comp.Image,
				DesiredState: comp.DesiredState,
				Version:      comp.Version,
			}

			if rc, ok := runtimeByName[containerName]; ok {
				ci.ObservedState = rc.State
				if !rc.Started.IsZero() {
					ci.Started = timestamppb.New(rc.Started)
				}
				matched[containerName] = true
			} else {
				ci.ObservedState = "removed"
			}

			info.Components = append(info.Components, ci)
		}

		result = append(result, info)
	}

	for _, c := range containers {
		if matched[c.Name] {
			continue
		}

		svcName, compName := SplitContainerName(c.Name, knownServices)

		result = append(result, &mcpv1.ServiceInfo{
			Name:   svcName,
			Active: false,
			Components: []*mcpv1.ComponentInfo{
				{
					Name:          compName,
					Image:         c.Image,
					DesiredState:  "ignore",
					ObservedState: c.State,
					Version:       c.Version,
				},
			},
		})
	}

	return result, nil
}

// LiveCheck queries the container runtime, reconciles against the registry,
// and returns the updated state for all services.
func (a *Agent) LiveCheck(ctx context.Context, req *mcpv1.LiveCheckRequest) (*mcpv1.LiveCheckResponse, error) {
	a.Logger.Debug("LiveCheck called")

	services, err := a.liveCheckServices(ctx)
	if err != nil {
		return nil, fmt.Errorf("live check: %w", err)
	}

	return &mcpv1.LiveCheckResponse{Services: services}, nil
}

// GetServiceStatus performs a live check, detects drift, and returns recent
// events. If a service name is provided, results are filtered to that service.
func (a *Agent) GetServiceStatus(ctx context.Context, req *mcpv1.GetServiceStatusRequest) (*mcpv1.GetServiceStatusResponse, error) {
	a.Logger.Debug("GetServiceStatus called", "service", req.GetName())

	services, err := a.liveCheckServices(ctx)
	if err != nil {
		return nil, fmt.Errorf("live check: %w", err)
	}

	if req.GetName() != "" {
		var filtered []*mcpv1.ServiceInfo
		for _, svc := range services {
			if svc.Name == req.GetName() {
				filtered = append(filtered, svc)
			}
		}
		services = filtered
	}

	var drift []*mcpv1.DriftInfo
	for _, svc := range services {
		for _, comp := range svc.Components {
			if comp.DesiredState == "ignore" {
				continue
			}
			if comp.DesiredState != comp.ObservedState {
				drift = append(drift, &mcpv1.DriftInfo{
					Service:       svc.Name,
					Component:     comp.Name,
					DesiredState:  comp.DesiredState,
					ObservedState: comp.ObservedState,
				})
			}
		}
	}

	since := time.Now().Add(-1 * time.Hour)
	svcFilter := req.GetName()
	events, err := registry.QueryEvents(a.DB, svcFilter, "", since, 50)
	if err != nil {
		return nil, fmt.Errorf("query events: %w", err)
	}

	var protoEvents []*mcpv1.EventInfo
	for _, e := range events {
		protoEvents = append(protoEvents, &mcpv1.EventInfo{
			Service:   e.Service,
			Component: e.Component,
			PrevState: e.PrevState,
			NewState:  e.NewState,
			Timestamp: timestamppb.New(e.Timestamp),
		})
	}

	return &mcpv1.GetServiceStatusResponse{
		Services:     services,
		Drift:        drift,
		RecentEvents: protoEvents,
	}, nil
}
