// Package service provides business logic services.
package service

import (
	"context"
	"fmt"
	"time"

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

// DiagnosticsServiceConfig configures the diagnostics service.
type DiagnosticsServiceConfig struct {
	// DefaultGitOwner is the git organization for CI lookups.
	DefaultGitOwner string

	// MaxRecentOperations is how many operations to include.
	MaxRecentOperations int

	// MaxRecentPipelines is how many pipelines to include.
	MaxRecentPipelines int
}

// DiagnosticsService aggregates project health information from multiple sources.
type DiagnosticsService struct {
	operationRepo   port.OperationRepository
	registryChecker port.RegistryChecker
	ciProvider      port.CIProvider

	defaultGitOwner     string
	maxRecentOperations int
	maxRecentPipelines  int
}

// NewDiagnosticsService creates a new diagnostics service.
func NewDiagnosticsService(
	operationRepo port.OperationRepository,
	registryChecker port.RegistryChecker,
	ciProvider port.CIProvider,
	cfg DiagnosticsServiceConfig,
) *DiagnosticsService {
	maxOps := cfg.MaxRecentOperations
	if maxOps <= 0 {
		maxOps = 10
	}

	maxPipelines := cfg.MaxRecentPipelines
	if maxPipelines <= 0 {
		maxPipelines = 5
	}

	return &DiagnosticsService{
		operationRepo:       operationRepo,
		registryChecker:     registryChecker,
		ciProvider:          ciProvider,
		defaultGitOwner:     cfg.DefaultGitOwner,
		maxRecentOperations: maxOps,
		maxRecentPipelines:  maxPipelines,
	}
}

// GetDiagnostics returns comprehensive health information for a project.
func (s *DiagnosticsService) GetDiagnostics(ctx context.Context, projectID string) (*domain.ProjectDiagnostics, error) {
	diag := &domain.ProjectDiagnostics{
		ProjectID:   projectID,
		GeneratedAt: time.Now().UTC(),
		Summary:     domain.DiagnosticsSummaryHealthy,
		Issues:      []domain.DiagnosticIssue{},
	}

	// Collect data from each source (don't fail if one source fails)
	s.collectOperations(ctx, projectID, diag)
	s.collectRegistryHealth(ctx, diag)
	s.collectCIStatus(ctx, projectID, diag)

	// Determine overall summary
	s.calculateSummary(diag)

	return diag, nil
}

// collectOperations fetches recent operations and extracts issues.
func (s *DiagnosticsService) collectOperations(ctx context.Context, projectID string, diag *domain.ProjectDiagnostics) {
	filter := domain.OperationFilters{
		ProjectID: projectID,
		Limit:     s.maxRecentOperations,
	}

	ops, err := s.operationRepo.List(ctx, filter)
	if err != nil {
		log := logging.FromContext(ctx).WithService("diagnostics")
		log.Warn("failed to fetch operations for diagnostics",
			logging.FieldError, err,
			logging.FieldProjectID, projectID,
		)
		diag.Issues = append(diag.Issues, domain.DiagnosticIssue{
			Severity: domain.DiagnosticSeverityWarning,
			Source:   domain.DiagnosticSourceOperation,
			Message:  "Unable to fetch operation history",
			Details:  err.Error(),
		})
		return
	}

	// Convert to summaries
	for _, op := range ops {
		summary := domain.OperationSummary{
			ID:          op.ID,
			Type:        op.Type,
			Status:      op.Status,
			StartedAt:   op.StartedAt,
			DurationMs:  op.DurationMs,
			Error:       op.Error,
			ExternalRef: op.ExternalRef,
		}
		diag.RecentOperations = append(diag.RecentOperations, summary)

		// Extract issues from failed operations
		if op.Status == domain.OperationStatusFailed {
			issue := domain.DiagnosticIssue{
				Severity:  domain.DiagnosticSeverityError,
				Source:    domain.DiagnosticSourceOperation,
				Message:   fmt.Sprintf("%s operation failed", op.Type),
				Timestamp: op.StartedAt,
			}
			if op.Error != "" {
				issue.Details = op.Error
			}
			if op.ExternalRef != "" {
				issue.Message += fmt.Sprintf(" (%s)", op.ExternalRef)
			}
			diag.Issues = append(diag.Issues, issue)
		}
	}
}

// collectRegistryHealth checks registry status.
func (s *DiagnosticsService) collectRegistryHealth(ctx context.Context, diag *domain.ProjectDiagnostics) {
	if s.registryChecker == nil {
		return
	}

	status := s.registryChecker.Check(ctx)
	diag.Registry = &status

	if !status.Healthy {
		diag.Issues = append(diag.Issues, domain.DiagnosticIssue{
			Severity:  domain.DiagnosticSeverityError,
			Source:    domain.DiagnosticSourceRegistry,
			Message:   "Container registry unhealthy",
			Details:   status.Error,
			Timestamp: status.LastChecked,
		})
	}
}

// collectCIStatus fetches CI pipeline information.
func (s *DiagnosticsService) collectCIStatus(ctx context.Context, projectID string, diag *domain.ProjectDiagnostics) {
	if s.ciProvider == nil {
		diag.CI = &domain.CIDiagnostics{Available: false}
		return
	}

	owner := s.defaultGitOwner
	if owner == "" {
		owner = "jordan" // fallback
	}

	ciDiag := &domain.CIDiagnostics{Available: true}

	pipelines, err := s.ciProvider.ListPipelines(ctx, owner, projectID)
	if err != nil {
		log := logging.FromContext(ctx).WithService("diagnostics")
		log.Warn("failed to fetch pipelines for diagnostics",
			logging.FieldError, err,
			logging.FieldProjectID, projectID,
		)
		ciDiag.Available = false
		diag.CI = ciDiag
		return
	}

	// Convert to summaries and find failures
	var lastFailure *domain.CIPipeline
	for i, p := range pipelines {
		if i >= s.maxRecentPipelines {
			break
		}

		summary := domain.CIPipelineSummary{
			Number:    p.Number,
			Status:    p.Status,
			Branch:    p.Branch,
			Commit:    p.Commit,
			StartedAt: p.Started,
		}
		if p.Finished.After(p.Started) {
			summary.Duration = p.Finished.Sub(p.Started).Round(time.Second).String()
		}
		ciDiag.RecentPipelines = append(ciDiag.RecentPipelines, summary)

		// Track the most recent failure
		if p.Status == "failure" && lastFailure == nil {
			lastFailure = p
		}
	}

	// Get details on the last failure
	if lastFailure != nil {
		failure := s.getFailureDetails(ctx, owner, projectID, lastFailure)
		ciDiag.LastFailure = failure

		// Add as issue
		issue := domain.DiagnosticIssue{
			Severity:  domain.DiagnosticSeverityError,
			Source:    domain.DiagnosticSourceCI,
			Message:   fmt.Sprintf("CI build #%d failed", lastFailure.Number),
			Timestamp: lastFailure.Finished,
		}
		if failure != nil && failure.FailedStep != "" {
			issue.Message += fmt.Sprintf(" at step '%s'", failure.FailedStep)
			if failure.Error != "" {
				issue.Details = failure.Error
			}
		}
		diag.Issues = append(diag.Issues, issue)
	}

	diag.CI = ciDiag
}

// getFailureDetails fetches step-level details for a failed pipeline.
func (s *DiagnosticsService) getFailureDetails(ctx context.Context, owner, repo string, pipeline *domain.CIPipeline) *domain.CIPipelineFailure {
	failure := &domain.CIPipelineFailure{
		Number:    pipeline.Number,
		Timestamp: pipeline.Finished,
	}

	steps, err := s.ciProvider.GetPipelineSteps(ctx, owner, repo, pipeline.Number)
	if err != nil {
		log := logging.FromContext(ctx).WithService("diagnostics")
		log.Warn("failed to fetch pipeline steps",
			logging.FieldError, err,
			"pipeline", pipeline.Number,
		)
		return failure
	}

	failure.URL = steps.URL

	// Find the failed step
	for _, step := range steps.Steps {
		if step.Status == "failure" || step.Status == "error" {
			failure.FailedStep = step.Name
			failure.Error = step.Error
			if step.Log != "" {
				failure.LogTail = step.Log
			}
			break
		}
	}

	return failure
}

// calculateSummary determines the overall health status.
func (s *DiagnosticsService) calculateSummary(diag *domain.ProjectDiagnostics) {
	errorCount := 0
	warningCount := 0

	for _, issue := range diag.Issues {
		switch issue.Severity {
		case domain.DiagnosticSeverityError:
			errorCount++
		case domain.DiagnosticSeverityWarning:
			warningCount++
		}
	}

	if errorCount > 0 {
		diag.Summary = domain.DiagnosticsSummaryUnhealthy
	} else if warningCount > 0 {
		diag.Summary = domain.DiagnosticsSummaryDegraded
	} else {
		diag.Summary = domain.DiagnosticsSummaryHealthy
	}
}
