package app

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

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

type ListProviderAccountsRequest struct {
	HostID         string
	ProviderID     string
	LogicalGroupID string
	RouteID        string
	ShadowGroupID  string
	AccountStatus  string
	BindingState   string
	Query          string
	Limit          int
}

type UpdateProviderAccountStatusRequest struct {
	AccountID      int64  `json:"-"`
	AccountStatus  string `json:"-"`
	DisabledReason string `json:"reason,omitempty"`
}

type GetProviderAccountBindingCandidatesRequest struct {
	AccountID int64
}

type UpdateProviderAccountBindingRequest struct {
	AccountID int64  `json:"-"`
	RouteID   string `json:"route_id,omitempty"`
	Clear     bool   `json:"clear,omitempty"`
}

type ProviderAccountBindingCandidatesResult struct {
	ProviderAccount ProviderAccountInfo     `json:"provider_account"`
	CandidateRoutes []LogicalGroupRouteInfo `json:"candidate_routes"`
}

type ProviderAccountInfo struct {
	ID                    int64  `json:"id"`
	HostID                string `json:"host_id"`
	HostBaseURL           string `json:"host_base_url"`
	ProviderID            string `json:"provider_id"`
	ProviderName          string `json:"provider_name"`
	RouteName             string `json:"route_name,omitempty"`
	RouteID               string `json:"route_id,omitempty"`
	LogicalGroupID        string `json:"logical_group_id,omitempty"`
	ShadowGroupID         string `json:"shadow_group_id,omitempty"`
	ShadowHostID          string `json:"shadow_host_id,omitempty"`
	UpstreamBaseURLHint   string `json:"upstream_base_url_hint,omitempty"`
	HostAccountID         string `json:"host_account_id"`
	KeyFingerprint        string `json:"key_fingerprint"`
	AccountName           string `json:"account_name"`
	AccountStatus         string `json:"account_status"`
	BindingState          string `json:"binding_state,omitempty"`
	BindingCandidateCount int    `json:"binding_candidate_count,omitempty"`
	LastProbeStatus       string `json:"last_probe_status,omitempty"`
	LastProbeAt           string `json:"last_probe_at,omitempty"`
	DisabledReason        string `json:"disabled_reason,omitempty"`
	CreatedAt             string `json:"created_at,omitempty"`
	UpdatedAt             string `json:"updated_at,omitempty"`
}

func handleListProviderAccounts(w http.ResponseWriter, r *http.Request, fn func(context.Context, ListProviderAccountsRequest) ([]ProviderAccountInfo, error)) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-provider-accounts action is not configured"})
		return
	}
	accounts, err := fn(r.Context(), ListProviderAccountsRequest{
		HostID:         strings.TrimSpace(r.URL.Query().Get("host_id")),
		ProviderID:     strings.TrimSpace(r.URL.Query().Get("provider_id")),
		LogicalGroupID: strings.TrimSpace(r.URL.Query().Get("logical_group_id")),
		RouteID:        strings.TrimSpace(r.URL.Query().Get("route_id")),
		ShadowGroupID:  strings.TrimSpace(r.URL.Query().Get("shadow_group_id")),
		AccountStatus:  strings.TrimSpace(r.URL.Query().Get("account_status")),
		BindingState:   strings.TrimSpace(r.URL.Query().Get("binding_state")),
		Query:          strings.TrimSpace(r.URL.Query().Get("q")),
		Limit:          parsePositiveInt(r.URL.Query().Get("limit")),
	})
	if err != nil {
		writeHTTPError(w, classifyError(err))
		return
	}
	if accounts == nil {
		accounts = []ProviderAccountInfo{}
	}
	writeJSON(w, http.StatusOK, map[string]any{"provider_accounts": accounts})
}

func handleGetProviderAccountBindingCandidates(w http.ResponseWriter, r *http.Request, fn func(context.Context, GetProviderAccountBindingCandidatesRequest) (ProviderAccountBindingCandidatesResult, error)) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "get-provider-account-binding-candidates action is not configured"})
		return
	}
	accountID, parseErr := parseProviderAccountID(r.PathValue("accountID"))
	if parseErr != nil {
		writeHTTPError(w, parseErr)
		return
	}
	result, actionErr := fn(r.Context(), GetProviderAccountBindingCandidatesRequest{AccountID: accountID})
	if actionErr != nil {
		writeHTTPError(w, classifyError(actionErr))
		return
	}
	writeJSON(w, http.StatusOK, result)
}

func handleEnableProviderAccount(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error)) {
	handleUpdateProviderAccountStatus(w, r, fn, sqlite.ProviderAccountStatusActive)
}

func handleDisableProviderAccount(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error)) {
	handleUpdateProviderAccountStatus(w, r, fn, sqlite.ProviderAccountStatusDisabled)
}

func handleRetireProviderAccount(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error)) {
	handleUpdateProviderAccountStatus(w, r, fn, sqlite.ProviderAccountStatusDeprecated)
}

func handleUpdateProviderAccountBinding(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateProviderAccountBindingRequest) (ProviderAccountInfo, error)) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "update-provider-account-binding action is not configured"})
		return
	}
	accountID, err := parseProviderAccountID(r.PathValue("accountID"))
	if err != nil {
		writeHTTPError(w, err)
		return
	}
	var req UpdateProviderAccountBindingRequest
	if r.ContentLength != 0 {
		if err := decodeJSON(r, &req); err != nil {
			writeHTTPError(w, err)
			return
		}
	}
	req.AccountID = accountID
	account, actionErr := fn(r.Context(), req)
	if actionErr != nil {
		writeHTTPError(w, classifyError(actionErr))
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"provider_account": account})
}

func handleUpdateProviderAccountStatus(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error), accountStatus string) {
	if fn == nil {
		writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "update-provider-account-status action is not configured"})
		return
	}
	accountID, err := parseProviderAccountID(r.PathValue("accountID"))
	if err != nil {
		writeHTTPError(w, err)
		return
	}
	req := UpdateProviderAccountStatusRequest{
		AccountID:     accountID,
		AccountStatus: accountStatus,
	}
	if r.ContentLength != 0 {
		if err := decodeJSON(r, &req); err != nil {
			writeHTTPError(w, err)
			return
		}
		req.AccountID = accountID
		req.AccountStatus = accountStatus
	}
	account, actionErr := fn(r.Context(), req)
	if actionErr != nil {
		writeHTTPError(w, classifyError(actionErr))
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"provider_account": account})
}

func parseProviderAccountID(rawID string) (int64, *httpError) {
	accountID, err := strconv.ParseInt(strings.TrimSpace(rawID), 10, 64)
	if err != nil || accountID <= 0 {
		return 0, &httpError{StatusCode: http.StatusBadRequest, Code: "invalid_request", Message: "account_id must be a positive integer"}
	}
	return accountID, nil
}

func buildListProviderAccountsAction(sqliteDSN string) func(context.Context, ListProviderAccountsRequest) ([]ProviderAccountInfo, error) {
	return func(ctx context.Context, req ListProviderAccountsRequest) ([]ProviderAccountInfo, error) {
		store, err := sqlite.Open(ctx, sqliteDSN)
		if err != nil {
			return nil, err
		}
		defer store.Close()

		if err := sqlite.SyncProviderAccountsFromLatestImportBatches(ctx, store); err != nil {
			return nil, err
		}
		rows, err := store.ProviderAccounts().List(ctx, sqlite.ProviderAccountListFilter{
			HostID:         req.HostID,
			ProviderID:     req.ProviderID,
			LogicalGroupID: req.LogicalGroupID,
			RouteID:        req.RouteID,
			ShadowGroupID:  req.ShadowGroupID,
			AccountStatus:  req.AccountStatus,
			BindingState:   req.BindingState,
			Query:          req.Query,
			Limit:          req.Limit,
		})
		if err != nil {
			return nil, err
		}
		result := make([]ProviderAccountInfo, 0, len(rows))
		for _, row := range rows {
			result = append(result, providerAccountViewToInfo(row))
		}
		return result, nil
	}
}

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

		account, err := store.ProviderAccounts().GetViewByID(ctx, req.AccountID)
		if err != nil {
			if err == sql.ErrNoRows {
				return ProviderAccountBindingCandidatesResult{}, fmt.Errorf("provider account %d not found", req.AccountID)
			}
			return ProviderAccountBindingCandidatesResult{}, err
		}
		candidates := make([]LogicalGroupRouteInfo, 0)
		if strings.TrimSpace(account.HostID) != "" && strings.TrimSpace(account.ShadowGroupID) != "" {
			routes, routeErr := store.LogicalGroupRoutes().ListByShadowBinding(ctx, account.HostID, account.ShadowGroupID)
			if routeErr != nil {
				return ProviderAccountBindingCandidatesResult{}, routeErr
			}
			for _, route := range routes {
				candidates = append(candidates, logicalGroupRouteRowToInfo(route, nil))
			}
		}
		return ProviderAccountBindingCandidatesResult{
			ProviderAccount: providerAccountViewToInfo(account),
			CandidateRoutes: candidates,
		}, nil
	}
}

func buildUpdateProviderAccountStatusAction(sqliteDSN, accountStatus string) func(context.Context, UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error) {
	return func(ctx context.Context, req UpdateProviderAccountStatusRequest) (ProviderAccountInfo, error) {
		store, err := sqlite.Open(ctx, sqliteDSN)
		if err != nil {
			return ProviderAccountInfo{}, err
		}
		defer store.Close()

		if err := store.ProviderAccounts().UpdateStatusByID(ctx, req.AccountID, accountStatus, strings.TrimSpace(req.DisabledReason)); err != nil {
			if err == sql.ErrNoRows {
				return ProviderAccountInfo{}, fmt.Errorf("provider account %d not found", req.AccountID)
			}
			return ProviderAccountInfo{}, err
		}
		updated, err := store.ProviderAccounts().GetViewByID(ctx, req.AccountID)
		if err != nil {
			return ProviderAccountInfo{}, err
		}
		return providerAccountViewToInfo(updated), nil
	}
}

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

		account, err := store.ProviderAccounts().GetByID(ctx, req.AccountID)
		if err != nil {
			if err == sql.ErrNoRows {
				return ProviderAccountInfo{}, fmt.Errorf("provider account %d not found", req.AccountID)
			}
			return ProviderAccountInfo{}, err
		}

		if req.Clear {
			if err := store.ProviderAccounts().UpdateBindingByID(ctx, account.ID, "", account.ShadowGroupID); err != nil {
				return ProviderAccountInfo{}, err
			}
		} else {
			routeID := strings.TrimSpace(req.RouteID)
			if routeID == "" {
				return ProviderAccountInfo{}, fmt.Errorf("route_id is required")
			}
			route, routeErr := store.LogicalGroupRoutes().GetByRouteID(ctx, routeID)
			if routeErr != nil {
				if routeErr == sql.ErrNoRows {
					return ProviderAccountInfo{}, fmt.Errorf("logical group route %q not found", routeID)
				}
				return ProviderAccountInfo{}, routeErr
			}
			if strings.TrimSpace(route.ShadowHostID) != strings.TrimSpace(accountHostIDForBinding(store, ctx, account)) {
				return ProviderAccountInfo{}, fmt.Errorf("route %q shadow_host_id does not match provider account host", routeID)
			}
			if strings.TrimSpace(account.ShadowGroupID) != "" && strings.TrimSpace(route.ShadowGroupID) != strings.TrimSpace(account.ShadowGroupID) {
				return ProviderAccountInfo{}, fmt.Errorf("route %q shadow_group_id does not match provider account shadow_group_id", routeID)
			}
			if err := store.ProviderAccounts().UpdateBindingByID(ctx, account.ID, route.RouteID, route.ShadowGroupID); err != nil {
				return ProviderAccountInfo{}, err
			}
		}

		updated, err := store.ProviderAccounts().GetViewByID(ctx, account.ID)
		if err != nil {
			return ProviderAccountInfo{}, err
		}
		return providerAccountViewToInfo(updated), nil
	}
}

func accountHostIDForBinding(store *sqlite.DB, ctx context.Context, account sqlite.ProviderAccount) string {
	if store == nil {
		return ""
	}
	hostRow, err := store.Hosts().GetByID(ctx, account.HostID)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(hostRow.HostID)
}

func providerAccountViewToInfo(row sqlite.ProviderAccountView) ProviderAccountInfo {
	return ProviderAccountInfo{
		ID:                    row.ID,
		HostID:                row.HostID,
		HostBaseURL:           row.HostBaseURL,
		ProviderID:            row.ProviderID,
		ProviderName:          row.ProviderName,
		RouteName:             row.RouteName,
		RouteID:               row.RouteID,
		LogicalGroupID:        row.LogicalGroupID,
		ShadowGroupID:         row.ShadowGroupID,
		ShadowHostID:          row.ShadowHostID,
		UpstreamBaseURLHint:   row.UpstreamBaseURLHint,
		HostAccountID:         row.HostAccountID,
		KeyFingerprint:        row.KeyFingerprint,
		AccountName:           row.AccountName,
		AccountStatus:         row.AccountStatus,
		BindingState:          row.BindingState,
		BindingCandidateCount: row.BindingCandidateCount,
		LastProbeStatus:       row.LastProbeStatus,
		LastProbeAt:           row.LastProbeAt,
		DisabledReason:        row.DisabledReason,
		CreatedAt:             row.CreatedAt,
		UpdatedAt:             row.UpdatedAt,
	}
}
