package deployer

import (
	"bytes"
	"context"
	"fmt"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/orchard9/rdev/internal/domain"
)

// UndeployComponent removes deployment resources for a specific component.
func (d *Deployer) UndeployComponent(ctx context.Context, projectName, componentPath string) error {
	// Build deployment name from project and component
	spec := domain.DeploySpec{
		ProjectName:   projectName,
		ComponentPath: componentPath,
	}
	deploymentName := spec.DeploymentName()
	ns := d.config.Namespace

	// Delete Ingress
	err := d.client.NetworkingV1().Ingresses(ns).Delete(ctx, deploymentName, metav1.DeleteOptions{})
	if err != nil && !errors.IsNotFound(err) {
		return fmt.Errorf("failed to delete ingress: %w", err)
	}

	// Delete Service
	err = d.client.CoreV1().Services(ns).Delete(ctx, deploymentName, metav1.DeleteOptions{})
	if err != nil && !errors.IsNotFound(err) {
		return fmt.Errorf("failed to delete service: %w", err)
	}

	// Delete Deployment
	err = d.client.AppsV1().Deployments(ns).Delete(ctx, deploymentName, metav1.DeleteOptions{})
	if err != nil && !errors.IsNotFound(err) {
		return fmt.Errorf("failed to delete deployment: %w", err)
	}

	// Delete Secret
	err = d.client.CoreV1().Secrets(ns).Delete(ctx, deploymentName+"-env", metav1.DeleteOptions{})
	if err != nil && !errors.IsNotFound(err) {
		return fmt.Errorf("failed to delete secret: %w", err)
	}

	return nil
}

// GetComponentStatus returns deployment status for a specific component.
func (d *Deployer) GetComponentStatus(ctx context.Context, projectName, componentPath string) (*domain.DeployStatus, error) {
	// Build deployment name from project and component
	spec := domain.DeploySpec{
		ProjectName:   projectName,
		ComponentPath: componentPath,
	}
	deploymentName := spec.DeploymentName()
	ns := d.config.Namespace

	deployment, err := d.client.AppsV1().Deployments(ns).Get(ctx, deploymentName, metav1.GetOptions{})
	if err != nil {
		if errors.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get deployment: %w", err)
	}

	// Determine status
	var status domain.DeploymentStatus
	switch {
	case deployment.Status.ReadyReplicas == *deployment.Spec.Replicas:
		status = domain.DeploymentStatusRunning
	case deployment.Status.UnavailableReplicas > 0:
		status = domain.DeploymentStatusFailed
	case deployment.Status.ReadyReplicas < *deployment.Spec.Replicas:
		status = domain.DeploymentStatusPending
	default:
		status = domain.DeploymentStatusUnknown
	}

	// Get URL from ingress
	var url string
	ingress, err := d.client.NetworkingV1().Ingresses(ns).Get(ctx, deploymentName, metav1.GetOptions{})
	if err == nil && len(ingress.Spec.Rules) > 0 {
		host := ingress.Spec.Rules[0].Host
		url = "https://" + host
	}

	return &domain.DeployStatus{
		ProjectName:   projectName,
		ComponentPath: componentPath,
		Image:         deployment.Spec.Template.Spec.Containers[0].Image,
		Replicas:      int(*deployment.Spec.Replicas),
		ReadyReplicas: int(deployment.Status.ReadyReplicas),
		URL:           url,
		Status:        status,
		CreatedAt:     deployment.CreationTimestamp.Time,
		UpdatedAt:     time.Now(),
	}, nil
}

// ListComponentStatuses returns deployment status for all components in a project.
func (d *Deployer) ListComponentStatuses(ctx context.Context, projectName string) (*domain.ProjectDeployStatus, error) {
	ns := d.config.Namespace

	// List all deployments for this project
	deployments, err := d.client.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{
		LabelSelector: fmt.Sprintf("project=%s", projectName),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to list deployments: %w", err)
	}

	result := &domain.ProjectDeployStatus{
		ProjectName: projectName,
		Components:  make([]domain.ComponentDeployStatus, 0, len(deployments.Items)),
	}

	for _, dep := range deployments.Items {
		componentPath := dep.Labels["component"]
		componentName := dep.Name
		if componentPath == "" {
			// This is the main project deployment, not a component
			componentName = projectName
		}

		// Determine status
		var status domain.DeploymentStatus
		switch {
		case dep.Status.ReadyReplicas == *dep.Spec.Replicas:
			status = domain.DeploymentStatusRunning
		case dep.Status.UnavailableReplicas > 0:
			status = domain.DeploymentStatusFailed
		case dep.Status.ReadyReplicas < *dep.Spec.Replicas:
			status = domain.DeploymentStatusPending
		default:
			status = domain.DeploymentStatusUnknown
		}

		// Get URL from ingress
		var url string
		ingress, err := d.client.NetworkingV1().Ingresses(ns).Get(ctx, dep.Name, metav1.GetOptions{})
		if err == nil && len(ingress.Spec.Rules) > 0 {
			url = "https://" + ingress.Spec.Rules[0].Host
			// Set overall URL to first component URL
			if result.OverallURL == "" {
				result.OverallURL = url
			}
		}

		// Determine component type from path
		componentType := "unknown"
		if componentPath != "" {
			parts := splitComponentPath(componentPath)
			if len(parts) > 0 {
				switch parts[0] {
				case "services":
					componentType = "service"
				case "workers":
					componentType = "worker"
				case "apps":
					componentType = "app"
				case "cli":
					componentType = "cli"
				}
			}
		}

		result.Components = append(result.Components, domain.ComponentDeployStatus{
			ComponentPath: componentPath,
			ComponentName: componentName,
			ComponentType: componentType,
			Image:         dep.Spec.Template.Spec.Containers[0].Image,
			Replicas:      int(*dep.Spec.Replicas),
			ReadyReplicas: int(dep.Status.ReadyReplicas),
			URL:           url,
			Status:        status,
		})
	}

	return result, nil
}

// splitComponentPath splits a component path like "services/auth-api" into ["services", "auth-api"].
func splitComponentPath(path string) []string {
	var parts []string
	current := ""
	for _, c := range path {
		if c == '/' {
			if current != "" {
				parts = append(parts, current)
				current = ""
			}
		} else {
			current += string(c)
		}
	}
	if current != "" {
		parts = append(parts, current)
	}
	return parts
}

// RestartComponent triggers a rolling restart of a specific component.
func (d *Deployer) RestartComponent(ctx context.Context, projectName, componentPath string) error {
	// Build deployment name
	spec := domain.DeploySpec{
		ProjectName:   projectName,
		ComponentPath: componentPath,
	}
	deploymentName := spec.DeploymentName()
	ns := d.config.Namespace

	deployment, err := d.client.AppsV1().Deployments(ns).Get(ctx, deploymentName, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("failed to get deployment: %w", err)
	}

	// Add annotation to trigger rollout
	if deployment.Spec.Template.Annotations == nil {
		deployment.Spec.Template.Annotations = make(map[string]string)
	}
	deployment.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)

	_, err = d.client.AppsV1().Deployments(ns).Update(ctx, deployment, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to update deployment: %w", err)
	}

	return nil
}

// ScaleComponent adjusts the replica count for a component.
func (d *Deployer) ScaleComponent(ctx context.Context, projectName, componentPath string, replicas int) error {
	// Build deployment name
	spec := domain.DeploySpec{
		ProjectName:   projectName,
		ComponentPath: componentPath,
	}
	deploymentName := spec.DeploymentName()
	ns := d.config.Namespace

	scale, err := d.client.AppsV1().Deployments(ns).GetScale(ctx, deploymentName, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("failed to get scale: %w", err)
	}

	scale.Spec.Replicas = int32(replicas)

	_, err = d.client.AppsV1().Deployments(ns).UpdateScale(ctx, deploymentName, scale, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to update scale: %w", err)
	}

	return nil
}

// GetComponentLogs returns recent logs from a specific component's pods.
func (d *Deployer) GetComponentLogs(ctx context.Context, projectName, componentPath string, tailLines int) (string, error) {
	// Build deployment name
	spec := domain.DeploySpec{
		ProjectName:   projectName,
		ComponentPath: componentPath,
	}
	deploymentName := spec.DeploymentName()
	ns := d.config.Namespace

	// List pods for the component deployment
	pods, err := d.client.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
		LabelSelector: fmt.Sprintf("app=%s", deploymentName),
	})
	if err != nil {
		return "", fmt.Errorf("failed to list pods: %w", err)
	}

	if len(pods.Items) == 0 {
		return "", fmt.Errorf("no pods found for component %s in project %s", componentPath, projectName)
	}

	// Get logs from the first pod
	tail := int64(tailLines)
	opts := &corev1.PodLogOptions{
		TailLines: &tail,
	}

	req := d.client.CoreV1().Pods(ns).GetLogs(pods.Items[0].Name, opts)
	logs, err := req.Stream(ctx)
	if err != nil {
		return "", fmt.Errorf("failed to get logs: %w", err)
	}
	defer func() { _ = logs.Close() }()

	buf := new(bytes.Buffer)
	_, err = buf.ReadFrom(logs)
	if err != nil {
		return "", fmt.Errorf("failed to read logs: %w", err)
	}

	return buf.String(), nil
}
