package app

import (
	"context"
	"database/sql"
	"fmt"
	"net/http"
	"strings"

	"sub2api-cn-relay-manager/internal/batch"
	"sub2api-cn-relay-manager/internal/store/sqlite"
)

func handleGetBatchImportRun(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) (batch.RunSummaryProjection, error)) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "get-batch-import-run action is not configured"})
		return
	}
	runID := strings.TrimSpace(r.PathValue("run_id"))
	if runID == "" {
		writeHTTPError(w, &httpError{StatusCode: http.StatusBadRequest, Code: "invalid_request", Message: "run_id is required"})
		return
	}
	run, err := fn(r.Context(), runID)
	if err != nil {
		writeHTTPError(w, classifyError(err))
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"run":             run,
		"recent_warnings": run.RecentWarnings,
	})
}

func handleListBatchImportRunItems(w http.ResponseWriter, r *http.Request, fn func(context.Context, ListBatchImportRunItemsRequest) (ListBatchImportRunItemsResponse, error)) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-batch-import-run-items action is not configured"})
		return
	}
	runID := strings.TrimSpace(r.PathValue("run_id"))
	if runID == "" {
		writeHTTPError(w, &httpError{StatusCode: http.StatusBadRequest, Code: "invalid_request", Message: "run_id is required"})
		return
	}
	req := ListBatchImportRunItemsRequest{
		RunID:               runID,
		CurrentStage:        strings.TrimSpace(r.URL.Query().Get("current_stage")),
		ConfirmationStatus:  strings.TrimSpace(r.URL.Query().Get("confirmation_status")),
		AccessStatus:        strings.TrimSpace(r.URL.Query().Get("access_status")),
		ProviderID:          strings.TrimSpace(r.URL.Query().Get("provider_id")),
		MatchedAccountState: strings.TrimSpace(r.URL.Query().Get("matched_account_state")),
		AccountResolution:   strings.TrimSpace(r.URL.Query().Get("account_resolution")),
		Query:               strings.TrimSpace(r.URL.Query().Get("q")),
		Cursor:              strings.TrimSpace(r.URL.Query().Get("cursor")),
		Limit:               parsePositiveInt(r.URL.Query().Get("limit")),
	}
	if hasWarningRaw := strings.TrimSpace(r.URL.Query().Get("has_warning")); hasWarningRaw != "" {
		value := strings.EqualFold(hasWarningRaw, "true")
		req.HasWarning = &value
	}
	items, err := fn(r.Context(), req)
	if err != nil {
		writeHTTPError(w, classifyError(err))
		return
	}
	if items.Items == nil {
		items.Items = []batch.ItemSummaryProjection{}
	}
	writeJSON(w, http.StatusOK, items)
}

func handleGetBatchImportRunItem(w http.ResponseWriter, r *http.Request, fn func(context.Context, GetBatchImportRunItemRequest) (batch.ItemDetailProjection, error)) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "get-batch-import-run-item action is not configured"})
		return
	}
	req := GetBatchImportRunItemRequest{
		RunID:  strings.TrimSpace(r.PathValue("run_id")),
		ItemID: strings.TrimSpace(r.PathValue("item_id")),
	}
	if req.RunID == "" || req.ItemID == "" {
		writeHTTPError(w, &httpError{StatusCode: http.StatusBadRequest, Code: "invalid_request", Message: "run_id and item_id are required"})
		return
	}
	item, err := fn(r.Context(), req)
	if err != nil {
		writeHTTPError(w, classifyError(err))
		return
	}
	writeJSON(w, http.StatusOK, item)
}

func buildGetBatchImportRunAction(sqliteDSN string) func(context.Context, string) (batch.RunSummaryProjection, error) {
	return func(ctx context.Context, runID string) (batch.RunSummaryProjection, error) {
		store, err := sqlite.Open(ctx, sqliteDSN)
		if err != nil {
			return batch.RunSummaryProjection{}, err
		}
		defer store.Close()

		run, err := store.ImportRuns().GetByRunID(ctx, runID)
		if err != nil {
			if err == sql.ErrNoRows {
				return batch.RunSummaryProjection{}, fmt.Errorf("run not found: %s", runID)
			}
			return batch.RunSummaryProjection{}, err
		}
		return batch.ProjectRunSummary(run), nil
	}
}

func buildListBatchImportRunItemsAction(sqliteDSN string) func(context.Context, ListBatchImportRunItemsRequest) (ListBatchImportRunItemsResponse, error) {
	return func(ctx context.Context, req ListBatchImportRunItemsRequest) (ListBatchImportRunItemsResponse, error) {
		store, err := sqlite.Open(ctx, sqliteDSN)
		if err != nil {
			return ListBatchImportRunItemsResponse{}, err
		}
		defer store.Close()

		if _, err := store.ImportRuns().GetByRunID(ctx, req.RunID); err != nil {
			if err == sql.ErrNoRows {
				return ListBatchImportRunItemsResponse{}, fmt.Errorf("run not found: %s", req.RunID)
			}
			return ListBatchImportRunItemsResponse{}, err
		}
		items, err := store.ImportRunItems().ListByRunID(ctx, req.RunID)
		if err != nil {
			return ListBatchImportRunItemsResponse{}, err
		}

		limit := defaultPositiveInt(req.Limit, 50)
		result := make([]batch.ItemSummaryProjection, 0, limit)
		nextCursor := (*string)(nil)
		started := strings.TrimSpace(req.Cursor) == ""
		for _, item := range items {
			if !started {
				if item.ItemID == strings.TrimSpace(req.Cursor) {
					started = true
				}
				continue
			}
			view := batch.ProjectItemSummary(item)
			if !matchesItemFilters(view, req) {
				continue
			}
			if len(result) >= limit {
				cursor := item.ItemID
				nextCursor = &cursor
				break
			}
			result = append(result, view)
		}
		return ListBatchImportRunItemsResponse{Items: result, NextCursor: nextCursor}, nil
	}
}

func buildGetBatchImportRunItemAction(sqliteDSN string) func(context.Context, GetBatchImportRunItemRequest) (batch.ItemDetailProjection, error) {
	return func(ctx context.Context, req GetBatchImportRunItemRequest) (batch.ItemDetailProjection, error) {
		store, err := sqlite.Open(ctx, sqliteDSN)
		if err != nil {
			return batch.ItemDetailProjection{}, err
		}
		defer store.Close()

		item, err := store.ImportRunItems().GetByItemID(ctx, req.ItemID)
		if err != nil {
			if err == sql.ErrNoRows {
				return batch.ItemDetailProjection{}, fmt.Errorf("item not found: %s", req.ItemID)
			}
			return batch.ItemDetailProjection{}, err
		}
		if item.RunID != req.RunID {
			return batch.ItemDetailProjection{}, fmt.Errorf("item not found in run %s", req.RunID)
		}
		events, err := store.ImportRunEvents().ListByItemID(ctx, req.ItemID)
		if err != nil {
			return batch.ItemDetailProjection{}, err
		}
		return batch.ProjectItemDetail(item, events)
	}
}

func matchesItemFilters(view batch.ItemSummaryProjection, req ListBatchImportRunItemsRequest) bool {
	if req.CurrentStage != "" && view.CurrentStage != req.CurrentStage {
		return false
	}
	if req.ConfirmationStatus != "" && view.ConfirmationStatus != req.ConfirmationStatus {
		return false
	}
	if req.AccessStatus != "" && view.AccessStatus != req.AccessStatus {
		return false
	}
	if req.ProviderID != "" && view.ProviderID != req.ProviderID {
		return false
	}
	if req.MatchedAccountState != "" && view.MatchedAccountState != req.MatchedAccountState {
		return false
	}
	if req.AccountResolution != "" && view.AccountResolution != req.AccountResolution {
		return false
	}
	if req.HasWarning != nil {
		hasWarning := len(view.AdvisoryMessages) > 0
		if hasWarning != *req.HasWarning {
			return false
		}
	}
	if req.Query != "" {
		query := strings.ToLower(req.Query)
		if !strings.Contains(strings.ToLower(view.ItemID), query) &&
			!strings.Contains(strings.ToLower(view.ProviderID), query) &&
			!strings.Contains(strings.ToLower(view.BaseURL), query) {
			return false
		}
	}
	return true
}
