import { ExceptionHandler } from "@/core/errors";
import { ApiError, AuthApi, authApi, ErrorCode } from "@/data/services/api";
import {
  FacebookIdentityRequest,
  FacebookPsidLoginRequest,
  FacebookLoginRequest,
  FbIdLoginRequest,
  GoogleLoginRequest,
  GuestLoginRequest,
  GuestLoginResponse,
  LoginRequest,
  LoginResponse,
  LoginStatus,
  type LoginStatus as LoginStatusT,
  RefreshTokenRequest,
  RefreshTokenResponse,
  RegisterRequest,
} from "@/data/dto/auth";
import { User } from "@/data/dto/user";
import { AppEnvUtil } from "@/utils/app-env";
import { PlatformDetector } from "@/utils/platform-detect";
import { Result } from "@/utils/result";
import { Logger } from "@/utils/logger";
import { deviceIdentifier } from "@/utils/device_identifier";
import type { IAuthRepository } from "@/data/repositories/interfaces";
import { AuthStorage, type IAuthStorage } from "@/data/storage/auth";
import { UserStorage, type IUserStorage } from "@/data/storage/user";
import { createLazySingleton } from "./lazy_singleton";

const log = new Logger("DataRepositoriesAuthRepository");

const TEST_ACCOUNT_FLAG = "isTestAccount";

export class AuthRepository implements IAuthRepository {
  constructor(
    private readonly api: AuthApi,
    private readonly storage: IAuthStorage,
    private readonly userStorage: IUserStorage,
  ) {}

  // ============ 公共方法 ============

  /**
   * 用户注册。
   * 注册成功不会自动登录（与 Dart 行为一致），调用方需自行调 `emailLogin`。
   */
  async register(input: {
    username: string;
    email: string;
    password: string;
    guestId?: string;
  }): Promise<Result<void>> {
    return Result.wrap(async () => {
      await this.api.register(
        RegisterRequest.from(
          withTestAccountFlag({
            username: input.username,
            email: input.email,
            password: input.password,
            platform: getAuthPlatform(),
            guestId: input.guestId ?? "",
          }),
        ),
      );
    });
  }

  /** 邮箱/用户名 + 密码登录。 */
  async emailLogin(input: {
    email?: string;
    username?: string;
    password: string;
    guestId?: string;
    psid?: string;
  }): Promise<Result<LoginResponse>> {
    return Result.wrap(async () => {
      const email = input.email?.trim() ?? "";
      const response = await this.api.emailLogin(
        LoginRequest.from(
          withTestAccountFlag({
            email,
            username: input.username,
            password: input.password,
            platform: getAuthPlatform(),
            guestId: input.guestId ?? "",
            psid: input.psid ?? "",
          }),
        ),
      );
      await this._saveLoginData(response, LoginStatus.Email);
      return response;
    });
  }

  /**
   * 退出真实用户登录并恢复游客态。
   *
   * 注意：业务上不允许从其他登录态退出到未登录态，所以这里不会删除
   * guestToken / deviceId。退出后会尝试重新跑游客登录，以便恢复游客 userId；
   * 如果游客登录接口失败但本地仍有 guestToken，则降级回本地游客态，避免
   * 用户从聊天页被打回 splash。
   */
  async logout(): Promise<Result<void>> {
    try {
      await this.api.logout();
    } catch (e) {
      log.warn("[AuthRepository] logout API failed, restoring guest anyway", e);
    }

    try {
      const authClearResult = await this.storage.clearBusinessAuthData();
      const userClearResult = await this.userStorage.clearUserData();
      if (Result.isErr(authClearResult)) return authClearResult;
      if (Result.isErr(userClearResult)) return userClearResult;
    } catch (e) {
      return Result.err(e);
    }

    try {
      const deviceId = await deviceIdentifier.getDeviceId();
      const guestResult = await this.guestLogin(deviceId);
      if (Result.isOk(guestResult)) return Result.ok(undefined);

      const hasGuestToken = await this.storage.hasGuestToken();
      if (Result.isErr(hasGuestToken)) return hasGuestToken;
      if (hasGuestToken.data) {
        const providerResult = await this.storage.setLoginProvider(
          LoginStatus.Guest,
        );
        if (Result.isErr(providerResult)) return providerResult;
        log.warn(
          "[AuthRepository] guest login failed after logout, falling back to local guest token",
          guestResult.error,
        );
        return Result.ok(undefined);
      }

      return Result.err(guestResult.error);
    } catch (e) {
      return Result.err(e);
    }
  }

  /**
   * 游客登录：使用 deviceId 换取 guest token。
   * 成功后写 guest token + deviceId + userId。
   */
  async guestLogin(deviceId: string): Promise<Result<GuestLoginResponse>> {
    log.debug("[AuthRepository.guestLogin] API call START", {
      deviceIdLength: deviceId.length,
      deviceIdPrefix: deviceId.slice(0, 8),
    });
    return Result.wrap(async () => {
      const response = await this.api.guestLogin(
        GuestLoginRequest.from(withTestAccountFlag({ deviceId })),
      );
      log.debug("[AuthRepository.guestLogin] API call SUCCESS (Zod parsed)", {
        hasToken: !!response.token,
        hasUser: !!response.user,
        userLastMessageAt: response.user?.lastMessageAt,
      });
      const tokenResult = await this.storage.setGuestToken(response.token);
      if (Result.isErr(tokenResult)) throw tokenResult.error;
      const providerResult = await this.storage.setLoginProvider(
        LoginStatus.Guest,
      );
      if (Result.isErr(providerResult)) throw providerResult.error;
      const deviceResult = await this.storage.setDeviceId(deviceId);
      if (Result.isErr(deviceResult)) throw deviceResult.error;
      if (response.userId) {
        const userIdResult = await this.userStorage.setUserId(response.userId);
        if (Result.isErr(userIdResult)) throw userIdResult.error;
      }
      return response;
    }).catch((e) => {
      const errorResult = ExceptionHandler.handle(e);
      log.error("[AuthRepository.guestLogin] API call FAILED", {
        errorCode: errorResult.code,
        errorMessage: errorResult.message,
      });
      return Result.err(e);
    });
  }

  /** Google 登录。 */
  async googleLogin(input: {
    idToken: string;
    guestId?: string;
    psid?: string;
  }): Promise<Result<LoginResponse>> {
    return this._socialLogin(LoginStatus.Google, () =>
      this.api.googleLogin(
        GoogleLoginRequest.from(
          withTestAccountFlag({
            idToken: input.idToken,
            platform: getAuthPlatform(),
            guestId: input.guestId ?? "",
            psid: input.psid ?? "",
          }),
        ),
      ),
    );
  }

  /** Facebook 登录（accessToken 流程）。 */
  async facebookLogin(input: {
    accessToken: string;
    guestId?: string;
    psid?: string;
  }): Promise<Result<LoginResponse>> {
    return this._socialLogin(LoginStatus.Facebook, () =>
      this.api.facebookLogin(
        FacebookLoginRequest.from(
          withTestAccountFlag({
            accessToken: input.accessToken,
            platform: getAuthPlatform(),
            guestId: input.guestId ?? "",
            psid: input.psid ?? "",
          }),
        ),
      ),
    );
  }

  /** 通过 ASID 登录。 */
  async facebookAsidLogin(input: {
    asid: string;
    avatarUrl?: string;
    psid?: string;
  }): Promise<Result<LoginResponse>> {
    return this._socialLogin(LoginStatus.Facebook, () =>
      this.api.facebookIdLogin(
        FbIdLoginRequest.from(
          withTestAccountFlag({
            fbId: input.asid,
            avatarUrl: input.avatarUrl ?? "",
            psid: input.psid ?? "",
          }),
        ),
      ),
    );
  }

  /** 绑定 Facebook ASID / PSID 到当前登录用户。 */
  async bindFacebookIdentity(input: {
    asid?: string;
    psid?: string;
  }): Promise<Result<void>> {
    if (!input.asid && !input.psid) return Result.ok(undefined);

    return Result.wrap(async () => {
      const response = await this.api.bindFacebookIdentity(
        FacebookIdentityRequest.from({
          asid: input.asid ?? "",
          psid: input.psid ?? "",
        }),
      );
      const data = response.toJson();
      await this._saveFacebookIdentity({
        asid: data.fbAsid,
        psid: data.fbPsid,
      });
    });
  }

  /** 通过 Facebook PSID 直接登录或返回游客态。 */
  async facebookPsidLogin(input: {
    psid: string;
    deviceId?: string;
    bindToGuest?: boolean;
  }): Promise<Result<LoginStatusT>> {
    return Result.wrap(async () => {
      const response = await this.api.facebookPsidLogin(
        FacebookPsidLoginRequest.from({
          psid: input.psid,
          deviceId: input.deviceId ?? "",
          bindToGuest: input.bindToGuest ?? true,
        }),
      );
      const data = response.toJson();
      await this._saveFacebookIdentity({
        asid: data.fbAsid,
        psid: data.fbPsid,
      });

      if (data.isGuest || !data.hasCompleteFacebookIdentity) {
        await this.storage.setGuestToken(data.token);
        await this.storage.setLoginProvider(LoginStatus.Guest);
        if (input.deviceId) {
          await this.storage.setDeviceId(input.deviceId);
        }
        const userId = data.userId || data.user?.id || "";
        if (userId) {
          await this.userStorage.setUserId(userId);
        }
        return LoginStatus.Guest;
      }

      await this._saveLoginData(
        LoginResponse.from({
          token: data.token,
          refreshToken: data.refreshToken,
          user: data.user ?? { id: data.userId },
        }),
        LoginStatus.Facebook,
      );
      return LoginStatus.Facebook;
    });
  }

  /**
   * 刷新 token：先读本地的 refresh token，空则直接返回错误；
   * 调用 API 成功后写回新的 login token + refresh token。
   */
  async refreshToken(): Promise<Result<RefreshTokenResponse>> {
    return Result.wrap(async () => {
      const existing = await this.storage.getRefreshToken();
      if (Result.isErr(existing)) throw existing.error;
      if (!existing.data) {
        throw new ApiError(
          "HTTP_UNAUTHORIZED",
          "No refresh token available",
          ErrorCode.httpUnauthorized,
        );
      }
      const response = await this.api.refreshToken(
        RefreshTokenRequest.from({ refreshToken: existing.data }),
      );
      const tokenResult = await this.storage.setLoginToken(response.token);
      if (Result.isErr(tokenResult)) throw tokenResult.error;
      if (response.refreshToken) {
        const refreshTokenResult = await this.storage.setRefreshToken(
          response.refreshToken,
        );
        if (Result.isErr(refreshTokenResult)) throw refreshTokenResult.error;
      }
      return response;
    });
  }

  /**
   * 获取当前登录用户。成功后尽力写本地 User 缓存（offline hydrate），
   * 缓存失败用 `Logger.warn` 记录，不影响主流程。
   */
  async getCurrentUser(): Promise<Result<User>> {
    return Result.wrap(async () => {
      const user = await this.api.getCurrentUser();
      const writeResult = await this.userStorage.setUser(user.toJson());
      if (!writeResult.success) {
        log.warn(
          "[AuthRepository] failed to cache current user",
          writeResult.error,
        );
      }
      return user;
    });
  }

  // ============ 私有助手 ============

  /**
   * 社交登录的公共包装：调 API → 调 `_saveLoginData` 持久化。
   * `_saveLoginData` 内部全部 best-effort，存储写失败不抛错。
   */
  private async _socialLogin(
    provider: LoginStatusT,
    call: () => Promise<LoginResponse>,
  ): Promise<Result<LoginResponse>> {
    return Result.wrap(async () => {
      const response = await call();
      await this._saveLoginData(response, provider);
      return response;
    });
  }

  /**
   * 持久化登录态：login token → refresh token → User 对象 → userId。
   * 全部 best-effort，错误用 `Logger.warn` 记录但不让登录「失败」——
   * 调用方已经从 API 拿到了 LoginResponse，缓存只是离线加速。
   */
  private async _saveLoginData(
    data: LoginResponse,
    provider: LoginStatusT,
  ): Promise<void> {
    const r1 = await this.storage.setLoginToken(data.token);
    if (!r1.success) {
      log.warn("[AuthRepository] setLoginToken failed", r1.error);
    }
    const providerResult = await this.storage.setLoginProvider(provider);
    if (!providerResult.success) {
      log.warn("[AuthRepository] setLoginProvider failed", providerResult.error);
    }
    if (data.refreshToken) {
      const r2 = await this.storage.setRefreshToken(data.refreshToken);
      if (!r2.success) {
        log.warn("[AuthRepository] setRefreshToken failed", r2.error);
      }
    }
    const r3 = await this.userStorage.setUser(data.user.toJson());
    if (!r3.success) {
      log.warn("[AuthRepository] setUser failed", r3.error);
    }
    const r4 = await this.userStorage.setUserId(data.user.id);
    if (!r4.success) {
      log.warn("[AuthRepository] setUserId failed", r4.error);
    }
  }

  private async _saveFacebookIdentity(input: {
    asid?: string;
    psid?: string;
  }): Promise<void> {
    if (input.asid) {
      const r = await this.storage.setAsid(input.asid);
      if (!r.success) {
        log.warn("[AuthRepository] setAsid failed", r.error);
      }
    }
    if (input.psid) {
      const r = await this.storage.setPsid(input.psid);
      if (!r.success) {
        log.warn("[AuthRepository] setPsid failed", r.error);
      }
    }
  }
}

/** 全局懒单例。 */
export const getAuthRepository = createLazySingleton<IAuthRepository>(
  () =>
    new AuthRepository(
      authApi,
      AuthStorage.getInstance(),
      UserStorage.getInstance(),
    ),
);

function withTestAccountFlag<T extends Record<string, unknown>>(
  payload: T,
): T & { isTestAccount: boolean } {
  return {
    ...payload,
    [TEST_ACCOUNT_FLAG]: !AppEnvUtil.isProduction(),
  };
}

function getAuthPlatform(): string {
  return PlatformDetector.getPlatform();
}
