package metrics

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"path"
	"strings"
	"sync"
	"time"

	smithyhttp "github.com/aws/smithy-go/transport/http"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
	registerOnce sync.Once

	awsRequestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_aws_requests_total",
		Help: "Total number of AWS requests by component, service, operation, and result.",
	}, []string{"component", "service", "operation", "result"})
	awsRequestDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    "app_aws_request_duration_seconds",
		Help:    "AWS request duration in seconds by component, service, and operation.",
		Buckets: prometheus.DefBuckets,
	}, []string{"component", "service", "operation"})
	awsErrorsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_aws_errors_total",
		Help: "Total number of AWS errors by class.",
	}, []string{"component", "service", "operation", "error_class"})
	awsTransferredBytesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_aws_transferred_bytes_total",
		Help: "Total AWS transferred bytes by component, service, operation, and direction.",
	}, []string{"component", "service", "operation", "direction"})

	queueMessagesReceivedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_queue_messages_received_total",
		Help: "Total number of queue messages received.",
	}, []string{"component", "queue_name"})
	queueMessagesDeletedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_queue_messages_deleted_total",
		Help: "Total number of queue messages deleted.",
	}, []string{"component", "queue_name"})
	queueVisibilityChangesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_queue_visibility_changes_total",
		Help: "Total number of queue visibility timeout changes.",
	}, []string{"component", "queue_name", "result"})
	queueSendTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_queue_send_total",
		Help: "Total number of queue send attempts.",
	}, []string{"component", "queue_name", "result"})
	queueRetriesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_queue_retries_total",
		Help: "Total number of queue retry/requeue attempts.",
	}, []string{"component", "queue_name", "result"})
	queueMessageBytesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_queue_message_bytes_total",
		Help: "Total queue message bytes observed on send and receive.",
	}, []string{"component", "queue_name", "direction"})
	queueDepth = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "app_queue_depth",
		Help: "Approximate queue depth from SQS attributes.",
	}, []string{"component", "queue_name"})

	workerJobsProcessedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_worker_jobs_processed_total",
		Help: "Total number of worker jobs processed.",
	}, []string{"component", "job_type", "result"})
	workerJobDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    "app_worker_job_duration_seconds",
		Help:    "Worker job processing duration in seconds.",
		Buckets: prometheus.DefBuckets,
	}, []string{"component", "job_type"})

	emailSendTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_email_send_total",
		Help: "Total number of email send attempts.",
	}, []string{"component", "provider", "result"})
	emailSendDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    "app_email_send_duration_seconds",
		Help:    "Email send duration in seconds.",
		Buckets: prometheus.DefBuckets,
	}, []string{"component", "provider"})

	fileUploadTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_file_upload_total",
		Help: "Total number of file upload operations.",
	}, []string{"component", "result"})
	fileDeleteTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_file_delete_total",
		Help: "Total number of file delete operations.",
	}, []string{"component", "result"})
	fileHeadTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_file_head_total",
		Help: "Total number of file head operations.",
	}, []string{"component", "result"})
	filePresignDownloadTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_file_presign_download_total",
		Help: "Total number of file presign download operations.",
	}, []string{"component", "result"})
	brandingLogoFetchTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_branding_logo_fetch_total",
		Help: "Total number of branding logo fetch operations.",
	}, []string{"component", "source", "result"})
	brandingLogoFetchDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    "app_branding_logo_fetch_duration_seconds",
		Help:    "Branding logo fetch duration in seconds.",
		Buckets: prometheus.DefBuckets,
	}, []string{"component", "source"})
	brandingRequestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "app_branding_requests_total",
		Help: "Total number of branding endpoint requests by endpoint, source, status, and result.",
	}, []string{"component", "endpoint", "source", "status_code", "result"})
	brandingRequestDurationSeconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name:    "app_branding_request_duration_seconds",
		Help:    "Branding endpoint request duration in seconds.",
		Buckets: prometheus.DefBuckets,
	}, []string{"component", "endpoint"})
)

func Register() {
	registerOnce.Do(func() {
		collectors := []prometheus.Collector{
			awsRequestsTotal,
			awsRequestDurationSeconds,
			awsErrorsTotal,
			awsTransferredBytesTotal,
			queueMessagesReceivedTotal,
			queueMessagesDeletedTotal,
			queueVisibilityChangesTotal,
			queueSendTotal,
			queueRetriesTotal,
			queueMessageBytesTotal,
			queueDepth,
			workerJobsProcessedTotal,
			workerJobDurationSeconds,
			emailSendTotal,
			emailSendDurationSeconds,
			fileUploadTotal,
			fileDeleteTotal,
			fileHeadTotal,
			filePresignDownloadTotal,
			brandingLogoFetchTotal,
			brandingLogoFetchDurationSeconds,
			brandingRequestsTotal,
			brandingRequestDurationSeconds,
		}
		for _, collector := range collectors {
			registerCollector(collector)
		}
	})
}

func PromHTTPHandler() http.Handler {
	Register()
	return promhttp.Handler()
}

func ObserveAWSRequest(component, service, operation string, err error, started time.Time) {
	Register()
	component = normalizeLabel(component, "worker")
	service = normalizeLabel(service, "unknown")
	operation = normalizeLabel(operation, "unknown")
	result := resultLabel(err)

	awsRequestsTotal.WithLabelValues(component, service, operation, result).Inc()
	awsRequestDurationSeconds.WithLabelValues(component, service, operation).Observe(time.Since(started).Seconds())
	if err != nil {
		awsErrorsTotal.WithLabelValues(component, service, operation, errorClass(err)).Inc()
	}
}

func ObserveAWSTransferredBytes(component, service, operation, direction string, bytes int64) {
	Register()
	if bytes <= 0 {
		return
	}
	component = normalizeLabel(component, "worker")
	service = normalizeLabel(service, "unknown")
	operation = normalizeLabel(operation, "unknown")
	direction = normalizeLabel(direction, "unknown")
	awsTransferredBytesTotal.WithLabelValues(component, service, operation, direction).Add(float64(bytes))
}

func ObserveQueueReceive(component, queueName string, messageCount int, messageBytes int64, err error, started time.Time) {
	ObserveAWSRequest(component, "sqs", "ReceiveMessage", err, started)
	if err != nil || messageCount <= 0 {
		return
	}
	component = normalizeLabel(component, "worker")
	queueName = normalizeLabel(queueName, "unknown")
	queueMessagesReceivedTotal.WithLabelValues(component, queueName).Add(float64(messageCount))
	if messageBytes > 0 {
		queueMessageBytesTotal.WithLabelValues(component, queueName, "receive").Add(float64(messageBytes))
	}
}

func ObserveQueueDeleteBatch(component, queueName string, deletedCount int, err error, started time.Time) {
	ObserveAWSRequest(component, "sqs", "DeleteMessageBatch", err, started)
	if deletedCount <= 0 {
		return
	}
	component = normalizeLabel(component, "worker")
	queueName = normalizeLabel(queueName, "unknown")
	queueMessagesDeletedTotal.WithLabelValues(component, queueName).Add(float64(deletedCount))
}

func ObserveQueueVisibilityChange(component, queueName string, err error, started time.Time) {
	ObserveAWSRequest(component, "sqs", "ChangeMessageVisibility", err, started)
	component = normalizeLabel(component, "worker")
	queueName = normalizeLabel(queueName, "unknown")
	result := resultLabel(err)
	queueVisibilityChangesTotal.WithLabelValues(component, queueName, result).Inc()
	queueRetriesTotal.WithLabelValues(component, queueName, result).Inc()
}

func ObserveQueueSend(component, queueName string, messageBytes int64, err error, started time.Time) {
	ObserveAWSRequest(component, "sqs", "SendMessage", err, started)
	component = normalizeLabel(component, "worker")
	queueName = normalizeLabel(queueName, "unknown")
	result := resultLabel(err)
	queueSendTotal.WithLabelValues(component, queueName, result).Inc()
	if err == nil && messageBytes > 0 {
		queueMessageBytesTotal.WithLabelValues(component, queueName, "send").Add(float64(messageBytes))
	}
}

func ObserveQueueDepth(component, queueName string, depth int64, err error, started time.Time) {
	ObserveAWSRequest(component, "sqs", "GetQueueAttributes", err, started)
	if err != nil {
		return
	}
	component = normalizeLabel(component, "worker")
	queueName = normalizeLabel(queueName, "unknown")
	queueDepth.WithLabelValues(component, queueName).Set(float64(depth))
}

func InitializeQueueSeries(component, queueName string) {
	Register()
	component = normalizeLabel(component, "worker")
	queueName = normalizeLabel(queueName, "unknown")

	queueMessagesReceivedTotal.WithLabelValues(component, queueName).Add(0)
	queueMessagesDeletedTotal.WithLabelValues(component, queueName).Add(0)
	queueVisibilityChangesTotal.WithLabelValues(component, queueName, "success").Add(0)
	queueVisibilityChangesTotal.WithLabelValues(component, queueName, "error").Add(0)
	queueSendTotal.WithLabelValues(component, queueName, "success").Add(0)
	queueSendTotal.WithLabelValues(component, queueName, "error").Add(0)
	queueRetriesTotal.WithLabelValues(component, queueName, "success").Add(0)
	queueRetriesTotal.WithLabelValues(component, queueName, "error").Add(0)
	queueMessageBytesTotal.WithLabelValues(component, queueName, "send").Add(0)
	queueMessageBytesTotal.WithLabelValues(component, queueName, "receive").Add(0)
	queueDepth.WithLabelValues(component, queueName).Set(0)
}

func ObserveWorkerJob(component, jobType string, err error, started time.Time) {
	Register()
	component = normalizeLabel(component, "worker")
	jobType = normalizeLabel(jobType, "unknown")
	workerJobsProcessedTotal.WithLabelValues(component, jobType, resultLabel(err)).Inc()
	workerJobDurationSeconds.WithLabelValues(component, jobType).Observe(time.Since(started).Seconds())
}

func ObserveEmailSend(component, provider string, err error, started time.Time) {
	Register()
	component = normalizeLabel(component, "worker")
	provider = normalizeLabel(provider, "unknown")
	emailSendTotal.WithLabelValues(component, provider, resultLabel(err)).Inc()
	emailSendDurationSeconds.WithLabelValues(component, provider).Observe(time.Since(started).Seconds())
}

func ObserveFileUpload(component string, err error) {
	Register()
	fileUploadTotal.WithLabelValues(normalizeLabel(component, "api"), resultLabel(err)).Inc()
}

func ObserveFileDelete(component string, err error) {
	Register()
	fileDeleteTotal.WithLabelValues(normalizeLabel(component, "api"), resultLabel(err)).Inc()
}

func ObserveFileHead(component string, err error) {
	Register()
	fileHeadTotal.WithLabelValues(normalizeLabel(component, "api"), resultLabel(err)).Inc()
}

func ObserveFilePresignDownload(component string, err error) {
	Register()
	filePresignDownloadTotal.WithLabelValues(normalizeLabel(component, "api"), resultLabel(err)).Inc()
}

func ObserveBrandingLogoFetch(component, source string, err error, started time.Time) {
	Register()
	component = normalizeLabel(component, "api")
	source = normalizeLabel(source, "unknown")
	brandingLogoFetchTotal.WithLabelValues(component, source, resultLabel(err)).Inc()
	brandingLogoFetchDurationSeconds.WithLabelValues(component, source).Observe(time.Since(started).Seconds())
}

func ObserveBrandingRequest(component, endpoint, source string, statusCode int, err error, started time.Time) {
	Register()
	component = normalizeLabel(component, "api")
	endpoint = normalizeLabel(endpoint, "unknown")
	source = normalizeLabel(source, "unknown")
	status := "0"
	if statusCode > 0 {
		status = normalizeLabel(fmt.Sprintf("%d", statusCode), "0")
	}
	brandingRequestsTotal.WithLabelValues(component, endpoint, source, status, resultLabel(err)).Inc()
	brandingRequestDurationSeconds.WithLabelValues(component, endpoint).Observe(time.Since(started).Seconds())
}

func QueueNameFromURL(queueURL string) string {
	raw := strings.TrimSpace(queueURL)
	if raw == "" {
		return "unknown"
	}
	parsed, err := url.Parse(raw)
	if err != nil {
		return "unknown"
	}
	name := strings.TrimSpace(path.Base(parsed.Path))
	if name == "" || name == "." || name == "/" {
		return "unknown"
	}
	return name
}

func registerCollector(collector prometheus.Collector) {
	if err := prometheus.Register(collector); err != nil {
		var alreadyRegistered prometheus.AlreadyRegisteredError
		if errors.As(err, &alreadyRegistered) {
			return
		}
		panic(err)
	}
}

func errorClass(err error) string {
	statusCode := statusCodeFromError(err)
	if statusCode >= 400 && statusCode < 500 {
		return "4xx"
	}
	if statusCode >= 500 && statusCode < 600 {
		return "5xx"
	}
	return "other"
}

func statusCodeFromError(err error) int {
	if err == nil {
		return 0
	}
	type statusCoder interface {
		HTTPStatusCode() int
	}
	var sc statusCoder
	if errors.As(err, &sc) {
		return sc.HTTPStatusCode()
	}
	var respErr *smithyhttp.ResponseError
	if errors.As(err, &respErr) {
		return respErr.HTTPStatusCode()
	}
	return 0
}

func normalizeLabel(value, fallback string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	if value == "" {
		return fallback
	}
	return value
}

func resultLabel(err error) string {
	if err != nil {
		return "error"
	}
	return "success"
}
