package providers

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/niels/banking-app/backend/internal/audit"
	"github.com/niels/banking-app/backend/internal/domain"
	"github.com/niels/banking-app/backend/internal/httpapi/middleware"
	"github.com/niels/banking-app/backend/internal/platform/httputil"
	"github.com/niels/banking-app/backend/internal/respond"
)

type Handler struct {
	repo  *Repository
	audit *audit.Repository
	log   *slog.Logger
}

type integrationRequest struct {
	ProviderType            string          `json:"provider_type"`
	ProviderName            string          `json:"provider_name"`
	Mode                    string          `json:"mode"`
	Status                  string          `json:"status"`
	Environment             string          `json:"environment"`
	OwnerTeam               string          `json:"owner_team"`
	ContractReference       string          `json:"contract_reference"`
	CredentialsReference    string          `json:"credentials_reference"`
	WebhookSecretReference  string          `json:"webhook_secret_reference"`
	APIBaseURL              string          `json:"api_base_url"`
	TimeoutMS               int             `json:"timeout_ms"`
	RetryMaxAttempts        int             `json:"retry_max_attempts"`
	CircuitFailureThreshold int             `json:"circuit_failure_threshold"`
	CircuitCooldownSeconds  int             `json:"circuit_cooldown_seconds"`
	DegradedMode            string          `json:"degraded_mode"`
	SandboxEnabled          bool            `json:"sandbox_enabled"`
	ProductionEnabled       bool            `json:"production_enabled"`
	LastVerifiedAt          string          `json:"last_verified_at"`
	NextReviewAt            string          `json:"next_review_at"`
	EvidenceReference       string          `json:"evidence_reference"`
	Metadata                json.RawMessage `json:"metadata"`
}

type sandboxRunRequest struct {
	ProviderType      string          `json:"provider_type"`
	ProviderName      string          `json:"provider_name"`
	SuiteName         string          `json:"suite_name"`
	Status            string          `json:"status"`
	PassedChecks      int             `json:"passed_checks"`
	FailedChecks      int             `json:"failed_checks"`
	SkippedChecks     int             `json:"skipped_checks"`
	EvidenceReference string          `json:"evidence_reference"`
	Summary           string          `json:"summary"`
	Metadata          json.RawMessage `json:"metadata"`
	StartedAt         string          `json:"started_at"`
	CompletedAt       string          `json:"completed_at"`
}

func NewHandler(repo *Repository, auditRepo *audit.Repository, log *slog.Logger) *Handler {
	return &Handler{repo: repo, audit: auditRepo, log: log}
}

func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
	dashboard, err := h.repo.Dashboard(r.Context(), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, dashboard)
}

func (h *Handler) Integrations(w http.ResponseWriter, r *http.Request) {
	items, err := h.repo.ListIntegrations(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("provider_type")),
		strings.TrimSpace(r.URL.Query().Get("mode")),
		strings.TrimSpace(r.URL.Query().Get("status")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"provider_integrations": items})
}

func (h *Handler) UpsertIntegration(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req integrationRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	lastVerifiedAt, err := parseOptionalTime(req.LastVerifiedAt, "last_verified_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	nextReviewAt, err := parseOptionalTime(req.NextReviewAt, "next_review_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	item, err := h.repo.UpsertIntegration(r.Context(), IntegrationParams{
		ProviderType:            req.ProviderType,
		ProviderName:            req.ProviderName,
		Mode:                    req.Mode,
		Status:                  req.Status,
		Environment:             req.Environment,
		OwnerTeam:               req.OwnerTeam,
		ContractReference:       req.ContractReference,
		CredentialsReference:    req.CredentialsReference,
		WebhookSecretReference:  req.WebhookSecretReference,
		APIBaseURL:              req.APIBaseURL,
		TimeoutMS:               req.TimeoutMS,
		RetryMaxAttempts:        req.RetryMaxAttempts,
		CircuitFailureThreshold: req.CircuitFailureThreshold,
		CircuitCooldownSeconds:  req.CircuitCooldownSeconds,
		DegradedMode:            req.DegradedMode,
		SandboxEnabled:          req.SandboxEnabled,
		ProductionEnabled:       req.ProductionEnabled,
		LastVerifiedAt:          lastVerifiedAt,
		NextReviewAt:            nextReviewAt,
		EvidenceReference:       req.EvidenceReference,
		Metadata:                req.Metadata,
		AdminUserID:             claims.Subject,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.provider_integration.upserted", "provider_integration", item.ID, map[string]any{
		"provider_type":      item.ProviderType,
		"provider_name":      item.ProviderName,
		"mode":               item.Mode,
		"status":             item.Status,
		"production_enabled": item.ProductionEnabled,
	})
	respond.JSON(w, http.StatusOK, item)
}

func (h *Handler) OutboundCalls(w http.ResponseWriter, r *http.Request) {
	items, err := h.repo.ListOutboundCalls(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("provider_type")),
		strings.TrimSpace(r.URL.Query().Get("provider_name")),
		strings.TrimSpace(r.URL.Query().Get("status")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"provider_outbound_calls": items})
}

func (h *Handler) WebhookEvents(w http.ResponseWriter, r *http.Request) {
	items, err := h.repo.ListWebhookEvents(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("provider_type")),
		strings.TrimSpace(r.URL.Query().Get("provider_name")),
		strings.TrimSpace(r.URL.Query().Get("status")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"provider_webhook_events": items})
}

func (h *Handler) SandboxTestRuns(w http.ResponseWriter, r *http.Request) {
	items, err := h.repo.ListSandboxTestRuns(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("provider_type")),
		strings.TrimSpace(r.URL.Query().Get("provider_name")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"provider_sandbox_test_runs": items})
}

func (h *Handler) CreateSandboxTestRun(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req sandboxRunRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	startedAt, err := parseOptionalTime(req.StartedAt, "started_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	completedAt, err := parseOptionalTime(req.CompletedAt, "completed_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	start := time.Now().UTC()
	if startedAt != nil {
		start = *startedAt
	}
	run, err := h.repo.CreateSandboxTestRun(r.Context(), SandboxRunParams{
		ProviderType:      req.ProviderType,
		ProviderName:      req.ProviderName,
		SuiteName:         req.SuiteName,
		Status:            req.Status,
		PassedChecks:      req.PassedChecks,
		FailedChecks:      req.FailedChecks,
		SkippedChecks:     req.SkippedChecks,
		EvidenceReference: req.EvidenceReference,
		Summary:           req.Summary,
		Metadata:          req.Metadata,
		StartedAt:         start,
		CompletedAt:       completedAt,
		AdminUserID:       claims.Subject,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.provider_sandbox_test_run.created", "provider_sandbox_test_run", run.ID, map[string]any{
		"provider_type": run.ProviderType,
		"provider_name": run.ProviderName,
		"suite_name":    run.SuiteName,
		"status":        run.Status,
	})
	respond.Created(w, run)
}

func parseOptionalTime(raw, field string) (*time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339, raw)
	if err != nil {
		return nil, fmt.Errorf("%w: %s must be RFC3339", domain.ErrValidation, field)
	}
	utc := parsed.UTC()
	return &utc, nil
}

func queryLimit(r *http.Request) int {
	limit := 50
	if raw := r.URL.Query().Get("limit"); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil {
			limit = parsed
		}
	}
	return limit
}

func (h *Handler) recordAudit(r *http.Request, adminUserID, eventType, targetType, targetID string, metadata any) {
	if h.audit == nil {
		return
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: &adminUserID,
		EventType:   eventType,
		TargetType:  targetType,
		TargetID:    targetID,
		Metadata:    metadata,
		RemoteIP:    httputil.RemoteIP(r),
		UserAgent:   r.UserAgent(),
	}); err != nil && h.log != nil {
		h.log.Error("audit record failed", "error", err)
	}
}
