// frontend/src/lib/models/MigrationModel.svelte.ts
// #region MigrationModel [C:4] [TYPE Model] [SEMANTICS migration,dashboard,dry-run,environment,screen-model]
// @BRIEF State model for the migration screen — declares atoms, invariants, and all actions.
// @REJECTED Inline $state + handler functions in MigrationPage/+page.svelte was rejected because it scattered business logic across event handlers, made testing impossible without DOM render, and violated the RSM pattern established in semantics-svelte §IIIa.
// @INVARIANT Changing source environment resets dashboard selection to empty.
// @INVARIANT Changing source environment resets databases, mappings, and suggestions.
// @INVARIANT Changing source environment clears dry-run result.
// @INVARIANT Migration execution is blocked unless source≠target, distinct, and ≥1 dashboard selected.
// @INVARIANT Dry-run must complete (dryRunResult != null) before advancing to execution step.
// @INVARIANT Password prompt only appears when selectedTask.status === "AWAITING_INPUT" with type "database_password".
// @STATE idle       — Initial state, environments loading.
// @STATE ready      — Environments loaded, awaiting user configuration.
// @STATE loading    — Fetching dashboards, databases, or dry-run in progress.
// @STATE review     — Dry-run completed, awaiting user decision.
// @STATE executing  — Migration task submitted, task runner active.
// @STATE error      — API call failed, error message available.
// @ACTION loadEnvironments()             — Fetches environments, pre-fills source from active context.
// @ACTION selectSourceEnv(envId)         — Sets source, triggers dashboard/database/mapping reset.
// @ACTION selectTargetEnv(envId)         — Sets target environment.
// @ACTION toggleDashboard(id)            — Toggles dashboard selection.
// @ACTION setReplaceDb(bool)             — Toggles database replacement option.
// @ACTION setFixCrossFilters(bool)       — Toggles cross-filter fixing.
// @ACTION fetchDatabases()               — Loads source/target databases, existing mappings, and AI suggestions.
// @ACTION saveMapping(sourceUuid, targetUuid) — Saves a database mapping pair.
// @ACTION calculateDryRun()              — Sends dry-run request, stores result, advances to step 3.
// @ACTION executeMigration()             — Validates preconditions, sends migration request, activates task view.
// @ACTION resumeMigration(passwords)     — Resumes a paused migration task with passwords.
// @ACTION goToStep(n)                    — Navigates wizard step, gated by step readiness.
// @ACTION clearError()                   — Clears the error message.
// @ACTION openLogViewer(task)            — Opens the log viewer modal for a task.
// @ACTION closeLogViewer()               — Closes the log viewer modal.
// @ACTION toggleTaskHistory()            — Toggles task history panel visibility.
// @ATOM showMigrateModal                 — Visibility of migration modal (used by dashboards page).
// @RELATION DEPENDS_ON -> [EXT:frontend:api]
// @RELATION BINDS_TO -> [selectedTask]
// @RELATION BINDS_TO -> [environmentContextStore]
// @RELATION CALLS -> [EXT:frontend:resumeTask]

import { api } from "$lib/api.js";
import { selectedTask } from "$lib/stores.svelte.js";
import { environmentContextStore } from "$lib/stores/environmentContext.svelte.js";
import { resumeTask } from "../../services/taskService.js";
import { t } from "$lib/i18n/index.svelte.js";

// ── Types ─────────────────────────────────────────────────────

type ScreenState = "idle" | "ready" | "loading" | "review" | "executing" | "error";

interface Environment {
  id: string;
  name: string;
  [key: string]: unknown;
}

interface Dashboard {
  id: string;
  [key: string]: unknown;
}

interface Database {
  uuid: string;
  database_name: string;
  [key: string]: unknown;
}

interface Mapping {
  id?: number;
  source_db_uuid?: string;
  target_db_uuid?: string;
  source_db_name?: string;
  target_db_name?: string;
  [key: string]: unknown;
}

interface DryRunResult {
  summary?: Record<string, unknown>;
  risk?: { score: number; level: string; items: unknown[] };
  selected_dashboard_titles?: string[];
  [key: string]: unknown;
}

interface DashboardSelection {
  selected_ids: string[];
  source_env_id: string;
  target_env_id: string;
  replace_db_config: boolean;
  fix_cross_filters: boolean;
}

interface LogViewerTask {
  id: string;
  status: string;
  [key: string]: unknown;
}

interface PasswordRequest {
  type: string;
  databases?: string[];
  error_message?: string;
}

interface ActiveTask {
  id: string;
  plugin_id?: string;
  status: string;
  logs?: unknown[];
  params?: Record<string, unknown>;
  input_request?: PasswordRequest;
  [key: string]: unknown;
}

export class MigrationModel {
  // ── Atoms (reactive state) ──────────────────────────────────────

  // Wizard step
  currentStep: number = $state(1);   // 1=environments, 2=dashboards, 3=review, 4=execute

  // Environments
  environments: Environment[] = $state([]);
  sourceEnvId: string = $state("");
  targetEnvId: string = $state("");

  // Migration options
  replaceDb: boolean = $state(false);
  fixCrossFilters: boolean = $state(true);

  // Dashboards
  dashboards: Dashboard[] = $state([]);
  selectedDashboardIds: string[] = $state([]);

  // Database mappings
  sourceDatabases: Database[] = $state([]);
  targetDatabases: Database[] = $state([]);
  mappings: Mapping[] = $state([]);
  suggestions: Record<string, unknown>[] = $state([]);

  // Loading & error
  loading: boolean = $state(true);
  error: string = $state("");
  fetchingDbs: boolean = $state(false);
  dryRunLoading: boolean = $state(false);

  // Dry-run result
  dryRunResult: DryRunResult | null = $state(null);

  // Task history panel
  showTaskHistory: boolean = $state(false);

  // Log viewer modal
  showLogViewer: boolean = $state(false);
  logViewerTaskId: string | null = $state(null);
  logViewerTaskStatus: string | null = $state(null);

  // Migration modal visibility (used by dashboards page)
  showMigrateModal: boolean = $state(false);

  // Password prompt
  showPasswordPrompt: boolean = $state(false);
  passwordPromptDatabases: string[] = $state([]);
  passwordPromptErrorMessage: string = $state("");

  // Store refs (read-only reactive bindings)
  selectedTaskStore = selectedTask;
  envContextStore = environmentContextStore;

  // ── Derived ─────────────────────────────────────────────────────

  /** Whether each wizard step is ready to proceed. */
  stepReady = $derived<Record<number, boolean>>({
    1: this.sourceEnvId !== "" && this.targetEnvId !== "" && this.sourceEnvId !== this.targetEnvId,
    2: this.selectedDashboardIds.length > 0,
    3: true, // review is always ready if we got here
  });

  /** Human-readable screen state derived from atoms. */
  screenState: ScreenState = $derived(
    this.loading ? "idle"
    : this.error ? "error"
    : this.dryRunLoading || this.fetchingDbs ? "loading"
    : this.dryRunResult ? "review"
    : this.currentStep >= 4 ? "executing"
    : "ready"
  );

  /** Whether environments are distinct and valid. */
  envsReady: boolean = $derived(this.stepReady[1]);

  /** Whether migration can be executed. */
  canExecute: boolean = $derived(this.dryRunResult != null && this.stepReady[2]);

  // ── Actions ─────────────────────────────────────────────────────

  /** Load environments list. Pre-fills source from active context. */
  async loadEnvironments(): Promise<void> {
    this.loading = true;
    this.error = "";
    try {
      this.environments = await api.getEnvironmentsList();
      const activeEnvId = this.envContextStore.current?.selectedEnvId;
      if (activeEnvId && this.environments.some((env: Environment) => env.id === activeEnvId)) {
        this.sourceEnvId = activeEnvId;
      }
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to load environments";
    } finally {
      this.loading = false;
    }
  }

  /**
   * Select source environment. Triggers dashboard refetch.
   * @INVARIANT Resets dashboard selection, databases, mappings, and dry-run result.
   */
  selectSourceEnv(envId: string): void {
    if (envId === this.sourceEnvId) return;
    this.sourceEnvId = envId;
    // @INVARIANT: reset dependent selections
    this.selectedDashboardIds = [];
    this.dashboards = [];
    this.sourceDatabases = [];
    this.targetDatabases = [];
    this.mappings = [];
    this.suggestions = [];
    this.dryRunResult = null;
    if (envId) this._fetchDashboards(envId);
  }

  /** Select target environment. Resets dry-run result. */
  selectTargetEnv(envId: string): void {
    if (envId === this.targetEnvId) return;
    this.targetEnvId = envId;
    this.dryRunResult = null;
  }

  /** Toggle database replacement option. Resets databases when toggling on. */
  setReplaceDb(val: boolean): void {
    this.replaceDb = val;
    if (!val) {
      this.sourceDatabases = [];
      this.targetDatabases = [];
      this.mappings = [];
      this.suggestions = [];
    }
    this.dryRunResult = null;
  }

  /** Toggle cross-filter fixing. */
  setFixCrossFilters(val: boolean): void {
    this.fixCrossFilters = val;
    this.dryRunResult = null;
  }

  /** Toggle a dashboard in the selection set. */
  toggleDashboard(id: string): void {
    if (this.selectedDashboardIds.includes(id)) {
      this.selectedDashboardIds = this.selectedDashboardIds.filter((i) => i !== id);
    } else {
      this.selectedDashboardIds = [...this.selectedDashboardIds, id];
    }
    this.dryRunResult = null;
  }

  /** Select all dashboards. */
  selectAllDashboards(): void {
    this.selectedDashboardIds = this.dashboards.map((d) => d.id);
    this.dryRunResult = null;
  }

  /** Deselect all dashboards. */
  deselectAllDashboards(): void {
    this.selectedDashboardIds = [];
    this.dryRunResult = null;
  }

  /** Fetch databases, mappings, and AI suggestions for current env pair. */
  async fetchDatabases(): Promise<void> {
    if (!this.sourceEnvId || !this.targetEnvId) return;
    this.fetchingDbs = true;
    this.error = "";
    try {
      const [src, tgt, maps, sugs] = await Promise.all([
        api.requestApi(`/environments/${this.sourceEnvId}/databases`),
        api.requestApi(`/environments/${this.targetEnvId}/databases`),
        api.requestApi(`/mappings?source_env_id=${this.sourceEnvId}&target_env_id=${this.targetEnvId}`),
        api.postApi("/mappings/suggest", {
          source_env_id: this.sourceEnvId,
          target_env_id: this.targetEnvId,
        }),
      ]);
      this.sourceDatabases = src;
      this.targetDatabases = tgt;
      this.mappings = maps;
      this.suggestions = sugs;
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to fetch databases";
    } finally {
      this.fetchingDbs = false;
    }
  }

  /** Save a database mapping between source and target. */
  async saveMapping(sourceUuid: string, targetUuid: string): Promise<void> {
    const sDb = this.sourceDatabases.find((d: Database) => d.uuid === sourceUuid);
    const tDb = this.targetDatabases.find((d: Database) => d.uuid === targetUuid);
    if (!sDb || !tDb) return;
    try {
      const savedMapping = await api.postApi("/mappings", {
        source_env_id: this.sourceEnvId,
        target_env_id: this.targetEnvId,
        source_db_uuid: sourceUuid,
        target_db_uuid: targetUuid,
        source_db_name: sDb.database_name,
        target_db_name: tDb.database_name,
      });
      this.mappings = [
        ...this.mappings.filter((m: Mapping) => m.source_db_uuid !== sourceUuid),
        savedMapping,
      ];
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to save mapping";
    }
  }

  /** Run dry-run analysis. Sets dryRunResult and advances to step 3 on success. */
  async calculateDryRun(): Promise<void> {
    if (!this._validatePreconditions()) return;
    this.error = "";
    this.dryRunLoading = true;
    try {
      this.dryRunResult = await api.postApi("/migration/dry-run", this._buildSelection());
      this.currentStep = 3;
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Dry-run failed";
      this.dryRunResult = null;
    } finally {
      this.dryRunLoading = false;
    }
  }

  /** Execute migration. Submits task and activates task runner view. */
  async executeMigration(endpoint: string = "/migration/execute"): Promise<void> {
    if (!this._validatePreconditions()) return;
    this.error = "";
    try {
      this.dryRunResult = null;
      const result = await api.postApi(endpoint, this._buildSelection());
      await new Promise((r) => setTimeout(r, 500));
      try {
        const task = await api.getTask(result.task_id);
        this.selectedTaskStore.set(task);
      } catch {
        this.selectedTaskStore.set({
          id: result.task_id,
          plugin_id: "superset-migration",
          status: "RUNNING",
          logs: [],
          params: {},
        });
      }
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Migration execution failed";
    }
  }

  /** Resume a paused migration task with database passwords. */
  async resumeMigration(passwords: Record<string, string>): Promise<void> {
    const task = this.selectedTaskStore.current;
    if (!task) return;
    try {
      await resumeTask(task.id, passwords);
      this.showPasswordPrompt = false;
    } catch (e: unknown) {
      this.passwordPromptErrorMessage = e instanceof Error ? e.message : (t.migration?.resume_failed || "Resume failed");
    }
  }

  /** Navigate to a wizard step. Gated by step readiness. */
  goToStep(step: number): void {
    if (
      step <= this.currentStep ||
      (step === 2 && this.stepReady[1]) ||
      (step === 3 && this.stepReady[1] && this.stepReady[2]) ||
      (step === 4 && this.dryRunResult)
    ) {
      this.currentStep = step;
    }
  }

  /** Clear error message. */
  clearError(): void {
    this.error = "";
  }

  /** Open log viewer for a task. */
  openLogViewer(task: LogViewerTask): void {
    this.logViewerTaskId = task.id;
    this.logViewerTaskStatus = task.status;
    this.showLogViewer = true;
  }

  /** Close log viewer. */
  closeLogViewer(): void {
    this.showLogViewer = false;
    this.logViewerTaskId = null;
    this.logViewerTaskStatus = null;
  }

  /** Toggle task history panel. */
  toggleTaskHistory(): void {
    this.showTaskHistory = !this.showTaskHistory;
  }

  /** Watch for password-prompt signals from active task. Call in an $effect. */
  checkPasswordPrompt(): void {
    const activeTask = this.selectedTaskStore.current;
    if (
      !activeTask ||
      activeTask.status !== "AWAITING_INPUT" ||
      !activeTask.input_request
    ) {
      return;
    }
    const req = activeTask.input_request;
    if (req.type === "database_password") {
      this.passwordPromptDatabases = req.databases || [];
      this.passwordPromptErrorMessage = req.error_message || "";
      this.showPasswordPrompt = true;
    }
  }

  // ── Private ─────────────────────────────────────────────────────

  /** Fetch dashboards for the given environment. Resets selection. */
  async _fetchDashboards(envId: string): Promise<void> {
    try {
      this.dashboards = await api.requestApi(`/environments/${envId}/dashboards`);
      this.selectedDashboardIds = [];   // @INVARIANT: reset on env change
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to fetch dashboards";
      this.dashboards = [];
    }
  }

  /** Build the DashboardSelection payload for API calls. */
  _buildSelection(): DashboardSelection {
    return {
      selected_ids: this.selectedDashboardIds,
      source_env_id: this.sourceEnvId,
      target_env_id: this.targetEnvId,
      replace_db_config: this.replaceDb,
      fix_cross_filters: this.fixCrossFilters,
    };
  }

  /** Validate preconditions for migration/dry-run. Returns false and sets error if invalid. */
  _validatePreconditions(): boolean {
    if (!this.sourceEnvId || !this.targetEnvId) {
      this.error = t.migration?.select_both_envs || "Please select both source and target environments.";
      return false;
    }
    if (this.sourceEnvId === this.targetEnvId) {
      this.error = t.migration?.different_envs || "Source and target environments must be different.";
      return false;
    }
    if (this.selectedDashboardIds.length === 0) {
      this.error = t.migration?.select_dashboards || "Please select at least one dashboard to migrate.";
      return false;
    }
    return true;
  }
}
// #endregion MigrationModel
