/**
 * CLI Installer slice — manages CLI installation status and install/update progress.
 */

import { api } from '@renderer/api';
import { createLogger } from '@shared/utils/logger';
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';

import type { AppState } from '../types';
import type { CliInstallationStatus, CliProviderId, CliProviderStatus } from '@shared/types';
import type { StateCreator } from 'zustand';

const logger = createLogger('Store:cliInstaller');

/** Max log lines to keep in UI (reserved for future use) */
const _MAX_LOG_LINES = 50;
export const MULTIMODEL_PROVIDER_IDS: CliProviderId[] = [
  'anthropic',
  'codex',
  'gemini',
  'opencode',
];

export function createLoadingMultimodelCliStatus(): CliInstallationStatus {
  const providers: CliProviderStatus[] = (
    [
      { providerId: 'anthropic', displayName: 'Anthropic' },
      { providerId: 'codex', displayName: 'Codex' },
      { providerId: 'gemini', displayName: 'Gemini' },
      { providerId: 'opencode', displayName: 'OpenCode (75+ LLM providers)' },
    ] as const
  ).map((provider) => ({
    ...provider,
    supported: false,
    authenticated: false,
    authMethod: null,
    verificationState: 'unknown' as const,
    modelVerificationState: 'idle' as const,
    statusMessage: 'Checking...',
    models: [],
    modelAvailability: [],
    canLoginFromUi: provider.providerId !== 'opencode',
    capabilities: {
      teamLaunch: false,
      oneShot: false,
      extensions: createDefaultCliExtensionCapabilities(),
    },
    backend: null,
  }));

  return {
    flavor: 'agent_teams_orchestrator',
    displayName: 'Agent CLI',
    supportsSelfUpdate: false,
    showVersionDetails: false,
    showBinaryPath: false,
    installed: true,
    installedVersion: null,
    binaryPath: null,
    launchError: null,
    latestVersion: null,
    updateAvailable: false,
    authLoggedIn: false,
    authStatusChecking: true,
    authMethod: null,
    providers,
  };
}

function isModelOnlyFallbackProviderStatus(provider: CliProviderStatus): boolean {
  return (
    provider.supported === false &&
    provider.authenticated === false &&
    provider.authMethod === null &&
    provider.verificationState === 'unknown' &&
    provider.models.length > 0 &&
    provider.backend == null &&
    (provider.availableBackends?.length ?? 0) === 0 &&
    provider.capabilities.teamLaunch === false
  );
}

function isHydratedMultimodelProviderStatus(provider: CliProviderStatus | undefined): boolean {
  if (!provider) {
    return false;
  }

  if (isModelOnlyFallbackProviderStatus(provider)) {
    return false;
  }

  return !(
    provider.supported === false &&
    provider.authenticated === false &&
    provider.authMethod === null &&
    provider.verificationState === 'unknown' &&
    provider.statusMessage === 'Checking...' &&
    provider.models.length === 0 &&
    provider.backend == null &&
    (provider.availableBackends?.length ?? 0) === 0
  );
}

export function getIncompleteMultimodelProviderIds(
  status: CliInstallationStatus | null
): CliProviderId[] {
  if (status?.flavor !== 'agent_teams_orchestrator' || !status.installed) {
    return [];
  }

  return status.providers
    .filter((provider) => !isHydratedMultimodelProviderStatus(provider))
    .map((provider) => provider.providerId);
}

export function getModelOnlyFallbackProviderIds(
  status: CliInstallationStatus | null
): CliProviderId[] {
  if (status?.flavor !== 'agent_teams_orchestrator' || !status.installed) {
    return [];
  }

  return status.providers
    .filter((provider) => isModelOnlyFallbackProviderStatus(provider))
    .map((provider) => provider.providerId);
}

export function mergeCliStatusPreservingHydratedProviders(
  current: CliInstallationStatus | null,
  incoming: CliInstallationStatus
): CliInstallationStatus {
  if (
    current?.flavor !== 'agent_teams_orchestrator' ||
    incoming.flavor !== 'agent_teams_orchestrator'
  ) {
    return incoming;
  }

  const currentProvidersById = new Map(
    current.providers.map((provider) => [provider.providerId, provider])
  );
  const incomingProviderIds = new Set(incoming.providers.map((provider) => provider.providerId));
  const providers = incoming.providers.map((incomingProvider) => {
    const currentProvider = currentProvidersById.get(incomingProvider.providerId);
    if (
      currentProvider &&
      isHydratedMultimodelProviderStatus(currentProvider) &&
      !isHydratedMultimodelProviderStatus(incomingProvider)
    ) {
      return currentProvider;
    }
    return incomingProvider;
  });

  for (const currentProvider of current.providers) {
    if (
      !incomingProviderIds.has(currentProvider.providerId) &&
      isHydratedMultimodelProviderStatus(currentProvider)
    ) {
      providers.push(currentProvider);
    }
  }

  const authenticatedProvider = providers.find((provider) => provider.authenticated) ?? null;

  return {
    ...incoming,
    providers,
    authLoggedIn: providers.some((provider) => provider.authenticated),
    authMethod: authenticatedProvider?.authMethod ?? null,
  };
}

function isMultimodelCliStatus(
  status: CliInstallationStatus | null | undefined
): status is CliInstallationStatus & { flavor: 'agent_teams_orchestrator' } {
  return status?.flavor === 'agent_teams_orchestrator';
}

function hasActiveProviderStatusLoading(
  providerLoading: Partial<Record<CliProviderId, boolean>>
): boolean {
  return Object.values(providerLoading).some((loading) => loading === true);
}

function getAuthenticatedProvider(providers: CliProviderStatus[]): CliProviderStatus | null {
  return providers.find((provider) => provider.authenticated) ?? null;
}

function buildMultimodelCliAuthState(params: {
  status: CliInstallationStatus;
  providers?: CliProviderStatus[];
  providerLoading?: Partial<Record<CliProviderId, boolean>>;
}): Pick<CliInstallationStatus, 'authLoggedIn' | 'authMethod' | 'authStatusChecking'> {
  const providers = params.providers ?? params.status.providers;
  const providerLoading = params.providerLoading ?? {};
  const authenticatedProvider = getAuthenticatedProvider(providers);

  return {
    authLoggedIn: providers.some((provider) => provider.authenticated),
    authMethod: authenticatedProvider?.authMethod ?? null,
    authStatusChecking: params.status.installed && hasActiveProviderStatusLoading(providerLoading),
  };
}

function getProviderDisplayName(providerId: CliProviderId): string {
  switch (providerId) {
    case 'anthropic':
      return 'Anthropic';
    case 'codex':
      return 'Codex';
    case 'gemini':
      return 'Gemini';
    case 'opencode':
      return 'OpenCode (75+ LLM providers)';
  }
}

function isRateLimitProviderStatusError(message: string): boolean {
  const lower = message.toLowerCase();
  return (
    lower.includes('429') ||
    lower.includes('rate limit') ||
    lower.includes('rate_limit') ||
    lower.includes('model cooldown') ||
    lower.includes('cooling down')
  );
}

function createProviderStatusErrorSnapshot(params: {
  providerId: CliProviderId;
  message: string;
  currentProvider?: CliProviderStatus;
}): CliProviderStatus {
  const currentProvider =
    params.currentProvider ??
    createLoadingMultimodelCliStatus().providers.find(
      (provider) => provider.providerId === params.providerId
    )!;
  const isRateLimited = isRateLimitProviderStatusError(params.message);

  return {
    ...currentProvider,
    providerId: params.providerId,
    displayName: currentProvider.displayName ?? getProviderDisplayName(params.providerId),
    authenticated: isRateLimited ? currentProvider.authenticated : false,
    authMethod: isRateLimited ? currentProvider.authMethod : null,
    verificationState: isRateLimited ? currentProvider.verificationState : 'error',
    statusMessage: isRateLimited
      ? '请求过于频繁，状态刷新暂时跳过；已保留上一次可用状态。'
      : params.message,
    detailMessage: null,
  };
}

// =============================================================================
// Slice Interface
// =============================================================================

export interface CliInstallerSlice {
  // State
  cliStatus: CliInstallationStatus | null;
  cliStatusLoading: boolean;
  cliProviderStatusLoading: Partial<Record<CliProviderId, boolean>>;
  cliStatusError: string | null;
  cliInstallerState:
    | 'idle'
    | 'checking'
    | 'downloading'
    | 'verifying'
    | 'installing'
    | 'completed'
    | 'error';
  cliDownloadProgress: number;
  cliDownloadTransferred: number;
  cliDownloadTotal: number;
  cliInstallerError: string | null;
  cliInstallerDetail: string | null;
  cliInstallerLogs: string[];
  cliInstallerRawChunks: string[];
  cliCompletedVersion: string | null;

  // Actions
  bootstrapCliStatus: (options?: { multimodelEnabled?: boolean }) => Promise<void>;
  fetchCliStatus: () => Promise<void>;
  fetchCliProviderStatus: (
    providerId: CliProviderId,
    options?: { silent?: boolean; epoch?: number; verifyModels?: boolean }
  ) => Promise<void>;
  invalidateCliStatus: () => Promise<void>;
  installCli: () => void;
}

let cliStatusInFlight: Promise<void> | null = null;
const cliProviderStatusInFlight = new Map<string, Promise<void>>();
let cliStatusEpoch = 0;
const cliProviderStatusSeq = new Map<CliProviderId, number>();

// =============================================================================
// Slice Creator
// =============================================================================

export const createCliInstallerSlice: StateCreator<AppState, [], [], CliInstallerSlice> = (
  set,
  get
) => ({
  // Initial state
  cliStatus: null,
  cliStatusLoading: false,
  cliProviderStatusLoading: {},
  cliStatusError: null,
  cliInstallerState: 'idle',
  cliDownloadProgress: 0,
  cliDownloadTransferred: 0,
  cliDownloadTotal: 0,
  cliInstallerError: null,
  cliInstallerDetail: null,
  cliInstallerLogs: [],
  cliInstallerRawChunks: [],
  cliCompletedVersion: null,

  bootstrapCliStatus: async (options) => {
    if (!api.cliInstaller) return;
    const multimodelEnabled = options?.multimodelEnabled ?? false;
    if (!multimodelEnabled) {
      return get().fetchCliStatus();
    }

    const epoch = ++cliStatusEpoch;
    const providerLoading = Object.fromEntries(
      MULTIMODEL_PROVIDER_IDS.map((providerId) => [providerId, true])
    ) as Partial<Record<CliProviderId, boolean>>;

    set({
      cliStatus: createLoadingMultimodelCliStatus(),
      cliStatusLoading: true,
      cliProviderStatusLoading: providerLoading,
      cliStatusError: null,
    });

    try {
      const metadata = await api.cliInstaller.getStatus();
      if (metadata.flavor !== 'agent_teams_orchestrator') {
        set((state) => {
          if (epoch !== cliStatusEpoch) {
            return {};
          }

          const mergedMetadata = mergeCliStatusPreservingHydratedProviders(
            state.cliStatus,
            metadata
          );

          return {
            cliStatus: mergedMetadata,
            cliStatusLoading: false,
            cliProviderStatusLoading: {},
            cliStatusError: state.cliStatusError,
          };
        });
        return;
      }

      const nextProviderLoading = Object.fromEntries(
        MULTIMODEL_PROVIDER_IDS.map((providerId) => [
          providerId,
          !isHydratedMultimodelProviderStatus(
            metadata.providers.find((provider) => provider.providerId === providerId)
          ),
        ])
      ) as Partial<Record<CliProviderId, boolean>>;
      const pendingProviderIds = MULTIMODEL_PROVIDER_IDS.filter(
        (providerId) => nextProviderLoading[providerId] === true
      );

      set((state) => {
        if (epoch !== cliStatusEpoch || !state.cliStatus) {
          return {};
        }

        const nextCliStatus = mergeCliStatusPreservingHydratedProviders(state.cliStatus, metadata);
        const nextAuthState = isMultimodelCliStatus(nextCliStatus)
          ? buildMultimodelCliAuthState({
              status: nextCliStatus,
              providerLoading: nextProviderLoading,
            })
          : null;

        return {
          cliStatus: nextAuthState
            ? {
                ...nextCliStatus,
                launchError: metadata.launchError ?? null,
                ...nextAuthState,
              }
            : nextCliStatus,
          cliStatusLoading: false,
          cliProviderStatusLoading: nextProviderLoading,
        };
      });

      if (!metadata.installed) {
        if (epoch === cliStatusEpoch) {
          set({
            cliProviderStatusLoading: {},
          });
        }
        return;
      }

      if (pendingProviderIds.length === 0) {
        return;
      }

      await Promise.allSettled(
        pendingProviderIds.map((providerId) =>
          get().fetchCliProviderStatus(providerId, {
            silent: false,
            epoch,
          })
        )
      );
      return;
    } catch (error) {
      logger.warn('Failed to hydrate CLI metadata during provider-first bootstrap:', error);
    }

    try {
      await Promise.allSettled(
        MULTIMODEL_PROVIDER_IDS.map((providerId) =>
          get().fetchCliProviderStatus(providerId, {
            silent: false,
            epoch,
          })
        )
      );
    } finally {
      if (epoch === cliStatusEpoch) {
        set({ cliStatusLoading: false });
      }
    }
  },

  fetchCliStatus: async () => {
    if (!api.cliInstaller) return;
    if (cliStatusInFlight) return cliStatusInFlight;

    const epoch = ++cliStatusEpoch;
    cliStatusInFlight = (async () => {
      set({ cliStatusLoading: true, cliStatusError: null });
      try {
        const status = await api.cliInstaller.getStatus();
        if (epoch !== cliStatusEpoch) {
          return;
        }
        set((state) => {
          const nextCliStatus = mergeCliStatusPreservingHydratedProviders(state.cliStatus, status);
          return {
            cliStatus: isMultimodelCliStatus(nextCliStatus)
              ? {
                  ...nextCliStatus,
                  ...buildMultimodelCliAuthState({
                    status: nextCliStatus,
                    providerLoading: {},
                  }),
                }
              : nextCliStatus,
            cliProviderStatusLoading: {},
          };
        });
        if (status.installed) {
          for (const provider of status.providers) {
            void get().fetchCliProviderStatus(provider.providerId, {
              silent: true,
              epoch,
            });
          }
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to check CLI status';
        logger.error('Failed to fetch CLI status:', error);
        set({ cliStatusError: message });
      } finally {
        set({ cliStatusLoading: false });
        cliStatusInFlight = null;
      }
    })();

    return cliStatusInFlight;
  },

  fetchCliProviderStatus: async (providerId, options) => {
    if (!api.cliInstaller) return;
    if (get().cliStatus && !get().cliStatus?.installed) {
      return;
    }
    const verifyModels = options?.verifyModels === true && providerId !== 'opencode';
    const requestKey = `${providerId}:${verifyModels ? 'verify' : 'status'}`;
    const inFlight = cliProviderStatusInFlight.get(requestKey);
    if (inFlight) return inFlight;

    const requestEpoch = options?.epoch ?? cliStatusEpoch;
    const requestSeq = (cliProviderStatusSeq.get(providerId) ?? 0) + 1;
    const silent = options?.silent === true;
    cliProviderStatusSeq.set(providerId, requestSeq);

    const request = (async () => {
      if (!silent) {
        set((state) => {
          const nextLoading = {
            ...state.cliProviderStatusLoading,
            [providerId]: true,
          };

          return {
            cliStatusError: null,
            cliProviderStatusLoading: nextLoading,
            cliStatus:
              state.cliStatus && isMultimodelCliStatus(state.cliStatus)
                ? {
                    ...state.cliStatus,
                    ...buildMultimodelCliAuthState({
                      status: state.cliStatus,
                      providerLoading: nextLoading,
                    }),
                  }
                : state.cliStatus,
          };
        });
      }

      try {
        const providerStatus = verifyModels
          ? await api.cliInstaller.verifyProviderModels(providerId)
          : await api.cliInstaller.getProviderStatus(providerId);
        set((state) => {
          const currentCliStatus = state.cliStatus;
          const nextLoading = silent
            ? state.cliProviderStatusLoading
            : {
                ...state.cliProviderStatusLoading,
                [providerId]: false,
              };

          if (
            requestEpoch !== cliStatusEpoch ||
            cliProviderStatusSeq.get(providerId) !== requestSeq
          ) {
            return { cliProviderStatusLoading: nextLoading };
          }

          if (!providerStatus || !currentCliStatus) {
            return { cliProviderStatusLoading: nextLoading };
          }

          const settledCliStatus: CliInstallationStatus = currentCliStatus;
          const hasProvider = settledCliStatus.providers.some(
            (provider) => provider.providerId === providerId
          );
          const nextProviders = hasProvider
            ? settledCliStatus.providers.map((provider) =>
                provider.providerId === providerId ? providerStatus : provider
              )
            : [...settledCliStatus.providers, providerStatus];
          const nextCliStatus = isMultimodelCliStatus(settledCliStatus)
            ? {
                ...settledCliStatus,
                providers: nextProviders,
                ...buildMultimodelCliAuthState({
                  status: settledCliStatus,
                  providers: nextProviders,
                  providerLoading: nextLoading,
                }),
              }
            : {
                ...settledCliStatus,
                providers: nextProviders,
                authLoggedIn: nextProviders.some((provider) => provider.authenticated),
                authMethod: getAuthenticatedProvider(nextProviders)?.authMethod ?? null,
              };

          return {
            cliStatus: nextCliStatus,
            cliProviderStatusLoading: nextLoading,
          };
        });
      } catch (error) {
        const message =
          error instanceof Error ? error.message : `Failed to refresh ${providerId} status`;
        logger.error(`Failed to fetch ${providerId} CLI status:`, error);
        set((state) => {
          const currentCliStatus = state.cliStatus;
          const nextLoading = silent
            ? state.cliProviderStatusLoading
            : {
                ...state.cliProviderStatusLoading,
                [providerId]: false,
              };

          if (
            requestEpoch !== cliStatusEpoch ||
            cliProviderStatusSeq.get(providerId) !== requestSeq
          ) {
            return { cliProviderStatusLoading: nextLoading };
          }

          if (!currentCliStatus) {
            return {
              cliStatusError: message,
              cliProviderStatusLoading: nextLoading,
            };
          }

          const settledCliStatus: CliInstallationStatus = currentCliStatus;
          const currentProvider =
            settledCliStatus.providers.find((provider) => provider.providerId === providerId) ??
            undefined;
          const nextProviders = settledCliStatus.providers.some(
            (provider) => provider.providerId === providerId
          )
            ? settledCliStatus.providers.map((provider) =>
                provider.providerId === providerId
                  ? createProviderStatusErrorSnapshot({
                      providerId,
                      message,
                      currentProvider,
                    })
                  : provider
              )
            : [
                ...currentCliStatus.providers,
                createProviderStatusErrorSnapshot({
                  providerId,
                  message,
                  currentProvider,
                }),
              ];

          return {
            cliStatusError: message,
            cliProviderStatusLoading: nextLoading,
            cliStatus: isMultimodelCliStatus(settledCliStatus)
              ? {
                  ...settledCliStatus,
                  providers: nextProviders,
                  ...buildMultimodelCliAuthState({
                    status: settledCliStatus,
                    providers: nextProviders,
                    providerLoading: nextLoading,
                  }),
                }
              : {
                  ...settledCliStatus,
                  providers: nextProviders,
                  authLoggedIn: nextProviders.some((provider) => provider.authenticated),
                  authMethod: getAuthenticatedProvider(nextProviders)?.authMethod ?? null,
                },
          };
        });
      } finally {
        cliProviderStatusInFlight.delete(requestKey);
      }
    })();

    cliProviderStatusInFlight.set(requestKey, request);
    return request;
  },

  invalidateCliStatus: async () => {
    await api.cliInstaller?.invalidateStatus();
  },

  installCli: () => {
    set({
      cliInstallerState: 'checking',
      cliInstallerError: null,
      cliInstallerDetail: null,
      cliInstallerLogs: [],
      cliInstallerRawChunks: [],
      cliDownloadProgress: 0,
      cliDownloadTransferred: 0,
      cliDownloadTotal: 0,
      cliCompletedVersion: null,
    });
    api.cliInstaller.install().catch((error) => {
      logger.error('Failed to install CLI:', error);
    });
  },
});
