package main

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type e2eOrganizationStatusUpdate struct {
	OrganizationID string `json:"organizationId"`
	Status         string `json:"status"`
}

type e2eOrganizationTeamStatusUpdate struct {
	OrganizationID string `json:"organizationId"`
	TeamID         string `json:"teamId"`
	Status         string `json:"status"`
}

type e2eOrganizationMemberStatusUpdate struct {
	OrganizationID string `json:"organizationId"`
	MemberID       string `json:"memberId"`
	Status         string `json:"status"`
}

type e2eOrganizationTeamSyncUpdate struct {
	OrganizationID   string `json:"organizationId"`
	TeamID           string `json:"teamId"`
	ExternalProvider string `json:"externalProvider"`
	ExternalID       string `json:"externalId"`
	SyncSource       string `json:"syncSource"`
}

type e2eOrganizationUnitSyncUpdate struct {
	OrganizationID   string `json:"organizationId"`
	UnitID           string `json:"unitId"`
	ExternalProvider string `json:"externalProvider"`
	ExternalID       string `json:"externalId"`
	SyncSource       string `json:"syncSource"`
}

type e2eOrganizationMemberSyncUpdate struct {
	OrganizationID   string `json:"organizationId"`
	MemberID         string `json:"memberId"`
	ExternalProvider string `json:"externalProvider"`
	ExternalID       string `json:"externalId"`
	SyncSource       string `json:"syncSource"`
}

type e2eOrganizationStateRequest struct {
	Organizations []e2eOrganizationStatusUpdate       `json:"organizations"`
	Teams         []e2eOrganizationTeamStatusUpdate   `json:"teams"`
	Members       []e2eOrganizationMemberStatusUpdate `json:"members"`
	SyncedTeams   []e2eOrganizationTeamSyncUpdate     `json:"syncedTeams"`
	SyncedUnits   []e2eOrganizationUnitSyncUpdate     `json:"syncedUnits"`
	SyncedMembers []e2eOrganizationMemberSyncUpdate   `json:"syncedMembers"`
}

func e2eOrganizationStateHandler(pool coredb.DB) http.HandlerFunc {
	type errorResponse struct {
		Error string `json:"error"`
	}

	writeJSON := func(w http.ResponseWriter, status int, value any) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(status)
		_ = json.NewEncoder(w).Encode(value)
	}

	normalizeStatus := func(value string) (string, bool) {
		switch strings.ToLower(strings.TrimSpace(value)) {
		case "active":
			return "active", true
		case "inactive":
			return "inactive", true
		case "suspended":
			return "suspended", true
		default:
			return "", false
		}
	}

	resolveSyncSource := func(value string) string {
		resolved := strings.TrimSpace(value)
		if resolved == "" {
			return "scim"
		}
		return resolved
	}

	type syncRunKey struct {
		organizationID   string
		externalProvider string
		syncSource       string
	}

	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}

		var request e2eOrganizationStateRequest
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid request body"})
			return
		}

		tx, err := pool.BeginTx(r.Context(), coredb.TxOptions{})
		if err != nil {
			writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "begin transaction failed"})
			return
		}
		defer func() { _ = tx.Rollback(r.Context()) }()

		now := time.Now().UTC()
		syncRuns := make(map[syncRunKey]string)
		syncModes := make(map[string]string)
		loadOrCreateSyncRun := func(organizationID string, externalProvider string, syncSource string) (string, error) {
			key := syncRunKey{
				organizationID:   organizationID,
				externalProvider: externalProvider,
				syncSource:       syncSource,
			}
			if syncRunID, ok := syncRuns[key]; ok {
				return syncRunID, nil
			}

			var directoryRevision int64
			if err := tx.QueryRow(r.Context(), `
SELECT directory_revision
FROM organization_settings
WHERE organization_id = $1
`, organizationID).Scan(&directoryRevision); err != nil {
				return "", err
			}

			syncRunID, err := identifiers.NewEntityID("odsr")
			if err != nil {
				return "", err
			}
			if _, err := tx.Exec(r.Context(), `
INSERT INTO organization_directory_sync_runs (
    id,
    organization_id,
    directory_revision,
    external_provider,
    sync_source,
    status,
    started_at,
    completed_at,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, $5, 'completed', $6, $6, $6, $6)
`, syncRunID, organizationID, directoryRevision, externalProvider, syncSource, now); err != nil {
				return "", err
			}
			syncRuns[key] = syncRunID
			return syncRunID, nil
		}
		resolveSyncMode := func(organizationID string) (string, error) {
			if syncMode, ok := syncModes[organizationID]; ok {
				return syncMode, nil
			}

			var directoryBackend string
			if err := tx.QueryRow(r.Context(), `
SELECT directory_backend
FROM organization_settings
WHERE organization_id = $1
`, organizationID).Scan(&directoryBackend); err != nil {
				return "", err
			}

			syncMode := "local"
			switch strings.TrimSpace(directoryBackend) {
			case "external_authority":
				syncMode = "authority"
			case "external_shadow":
				syncMode = "shadow"
			}
			syncModes[organizationID] = syncMode
			return syncMode, nil
		}

		for _, update := range request.Organizations {
			status, ok := normalizeStatus(update.Status)
			if !ok || strings.TrimSpace(update.OrganizationID) == "" {
				writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid organization status update"})
				return
			}
			if _, err := tx.Exec(r.Context(), `
UPDATE organization_settings
SET status = $2, updated_at = $3
WHERE organization_id = $1
`, strings.TrimSpace(update.OrganizationID), status, now); err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "update organization status failed"})
				return
			}
		}

		for _, update := range request.Teams {
			status, ok := normalizeStatus(update.Status)
			if !ok || strings.TrimSpace(update.OrganizationID) == "" || strings.TrimSpace(update.TeamID) == "" {
				writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid team status update"})
				return
			}
			if _, err := tx.Exec(r.Context(), `
UPDATE organization_teams
SET status = $3, updated_at = $4
WHERE organization_id = $1 AND id = $2
`, strings.TrimSpace(update.OrganizationID), strings.TrimSpace(update.TeamID), status, now); err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "update team status failed"})
				return
			}
		}

		for _, update := range request.Members {
			status, ok := normalizeStatus(update.Status)
			if !ok || strings.TrimSpace(update.OrganizationID) == "" || strings.TrimSpace(update.MemberID) == "" {
				writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid member status update"})
				return
			}
			if _, err := tx.Exec(r.Context(), `
UPDATE organization_members
SET status = $3, updated_at = $4
WHERE organization_id = $1 AND id = $2
`, strings.TrimSpace(update.OrganizationID), strings.TrimSpace(update.MemberID), status, now); err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "update member status failed"})
				return
			}
		}

		for _, update := range request.SyncedTeams {
			organizationID := strings.TrimSpace(update.OrganizationID)
			teamID := strings.TrimSpace(update.TeamID)
			externalProvider := strings.TrimSpace(update.ExternalProvider)
			externalID := strings.TrimSpace(update.ExternalID)
			if organizationID == "" ||
				teamID == "" ||
				externalProvider == "" ||
				externalID == "" {
				writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid synced team update"})
				return
			}
			syncSource := resolveSyncSource(update.SyncSource)
			syncRunID, err := loadOrCreateSyncRun(organizationID, externalProvider, syncSource)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "create team sync run failed"})
				return
			}
			syncMode, err := resolveSyncMode(organizationID)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "load team sync mode failed"})
				return
			}
			if _, err := tx.Exec(r.Context(), `
UPDATE organization_teams
SET source = 'synced',
    external_provider = $3,
    external_id = $4,
    synced_at = $5,
    sync_mode = $6,
    sync_source = $7,
    last_sync_run_id = $8,
    updated_at = $5
WHERE organization_id = $1
  AND id = $2
`, organizationID, teamID, externalProvider, externalID, now, syncMode, syncSource, syncRunID); err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "update team sync metadata failed"})
				return
			}
		}

		for _, update := range request.SyncedUnits {
			organizationID := strings.TrimSpace(update.OrganizationID)
			unitID := strings.TrimSpace(update.UnitID)
			externalProvider := strings.TrimSpace(update.ExternalProvider)
			externalID := strings.TrimSpace(update.ExternalID)
			if organizationID == "" ||
				unitID == "" ||
				externalProvider == "" ||
				externalID == "" {
				writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid synced unit update"})
				return
			}
			syncSource := resolveSyncSource(update.SyncSource)
			syncRunID, err := loadOrCreateSyncRun(organizationID, externalProvider, syncSource)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "create unit sync run failed"})
				return
			}
			syncMode, err := resolveSyncMode(organizationID)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "load unit sync mode failed"})
				return
			}
			if _, err := tx.Exec(r.Context(), `
UPDATE organization_units
SET external_provider = $3,
    external_id = $4,
    synced_at = $5,
    sync_mode = $6,
    sync_source = $7,
    last_sync_run_id = $8,
    updated_at = $5
WHERE organization_id = $1
  AND id = $2
`, organizationID, unitID, externalProvider, externalID, now, syncMode, syncSource, syncRunID); err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "update unit sync metadata failed"})
				return
			}
		}

		for _, update := range request.SyncedMembers {
			organizationID := strings.TrimSpace(update.OrganizationID)
			memberID := strings.TrimSpace(update.MemberID)
			externalProvider := strings.TrimSpace(update.ExternalProvider)
			externalID := strings.TrimSpace(update.ExternalID)
			if organizationID == "" ||
				memberID == "" ||
				externalProvider == "" ||
				externalID == "" {
				writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid synced member update"})
				return
			}
			syncSource := resolveSyncSource(update.SyncSource)
			syncRunID, err := loadOrCreateSyncRun(organizationID, externalProvider, syncSource)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "create member sync run failed"})
				return
			}
			syncMode, err := resolveSyncMode(organizationID)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "load member sync mode failed"})
				return
			}
			if _, err := tx.Exec(r.Context(), `
UPDATE organization_members
SET source = 'sync',
    external_provider = $3,
    external_id = $4,
    synced_at = $5,
    sync_mode = $6,
    sync_source = $7,
    last_sync_run_id = $8,
    updated_at = $5
WHERE organization_id = $1
  AND id = $2
`, organizationID, memberID, externalProvider, externalID, now, syncMode, syncSource, syncRunID); err != nil {
				writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "update member sync metadata failed"})
				return
			}
		}

		if err := tx.Commit(r.Context()); err != nil {
			writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "commit status updates failed"})
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"ok": true})
	}
}
