import {
  Controller,
  Post,
  Get,
  Route,
  Security,
  Tags,
  Path,
  Request,
  Response,
  Query,
  Body,
} from "tsoa";
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
import HttpSuccess from "../interfaces/http-success";
import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";

@Route("api/v1/org/keycloak-sync")
@Tags("Keycloak Sync")
@Security("bearerAuth")
@Response(
  HttpStatus.INTERNAL_SERVER_ERROR,
  "เกิดข้อผิดพลาด ไม่สามารถดำเนินการได้ กรุณาลองใหม่ในภายหลัง",
)
export class KeycloakSyncController extends Controller {
  private keycloakAttributeService = new KeycloakAttributeService();

  /**
   * Sync attributes for the current logged-in user
   *
   * @summary Sync profileId and rootDnaId to Keycloak for current user
   */
  @Post("sync-me")
  async syncCurrentUser(@Request() request: RequestWithUser) {
    const keycloakUserId = request.user.sub;

    if (!keycloakUserId) {
      throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
    }

    // Get attributes from database before sync
    const dbAttrs = await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);

    const success = await this.keycloakAttributeService.syncUserAttributes(keycloakUserId);

    if (!success) {
      throw new HttpError(
        HttpStatus.INTERNAL_SERVER_ERROR,
        "ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ กรุณาติดต่อผู้ดูแลระบบ",
      );
    }

    // Verify sync by fetching attributes from Keycloak after update
    const kcAttrsAfter =
      await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);

    return new HttpSuccess({
      message: "Sync ข้อมูลสำเร็จ",
      syncedToKeycloak: !!kcAttrsAfter?.profileId,
      databaseAttributes: dbAttrs,
      keycloakAttributesAfter: kcAttrsAfter,
    });
  }

  /**
   * Get current attributes of the logged-in user
   *
   * @summary Get current profileId and rootDnaId from Keycloak
   */
  @Get("my-attributes")
  async getMyAttributes(@Request() request: RequestWithUser) {
    const keycloakUserId = request.user.sub;

    if (!keycloakUserId) {
      throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
    }

    const keycloakAttributes =
      await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);
    const dbAttributes =
      await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);

    return new HttpSuccess({
      keycloakAttributes,
      databaseAttributes: dbAttributes,
    });
  }

  /**
   * Sync attributes for a specific profile (Admin only)
   *
   * @summary Sync profileId and rootDnaId to Keycloak by profile ID (ADMIN)
   *
   * @param {string} profileId  Profile ID
   * @param {string} profileType  Profile type (PROFILE or PROFILE_EMPLOYEE)
   */
  @Post("sync-profile/:profileId")
  async syncByProfileId(
    @Path() profileId: string,
    @Query() profileType: "PROFILE" | "PROFILE_EMPLOYEE" = "PROFILE",
  ) {
    if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
      throw new HttpError(
        HttpStatus.BAD_REQUEST,
        "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
      );
    }

    const success = await this.keycloakAttributeService.syncOnOrganizationChange(
      profileId,
      profileType,
    );

    if (!success) {
      throw new HttpError(
        HttpStatus.INTERNAL_SERVER_ERROR,
        "ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ หรือไม่พบข้อมูล profile",
      );
    }

    return new HttpSuccess({ message: "Sync ข้อมูลสำเร็จ" });
  }

  /**
   * Batch sync attributes for multiple profiles (Admin only)
   *
   * @summary Batch sync profileId and rootDnaId to Keycloak for multiple profiles (ADMIN)
   *
   * @param {request} request  Request body containing profileIds array and profileType
   */
  @Post("sync-profiles-batch")
  async syncByProfileIds(
    @Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" },
  ) {
    const { profileIds, profileType } = request;

    // Validate profileIds
    if (!profileIds || profileIds.length === 0) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า");
    }

    // Validate profileType
    if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
      throw new HttpError(
        HttpStatus.BAD_REQUEST,
        "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
      );
    }

    const result = {
      total: profileIds.length,
      success: 0,
      failed: 0,
      details: [] as Array<{ profileId: string; status: "success" | "failed"; error?: string }>,
    };

    // Process each profileId
    for (const profileId of profileIds) {
      try {
        const success = await this.keycloakAttributeService.syncOnOrganizationChange(
          profileId,
          profileType,
        );

        if (success) {
          result.success++;
          result.details.push({ profileId, status: "success" });
        } else {
          result.failed++;
          result.details.push({
            profileId,
            status: "failed",
            error: "Sync returned false - ไม่พบข้อมูล profile หรือ Keycloak user ID",
          });
        }
      } catch (error: any) {
        result.failed++;
        result.details.push({ profileId, status: "failed", error: error.message });
      }
    }

    return new HttpSuccess({
      message: "Batch sync เสร็จสิ้น",
      ...result,
    });
  }

  /**
   * Clear org DNA attributes for profiles (Admin only)
   *
   * @summary Clear org DNA attributes in Keycloak for given profiles (ADMIN)
   *
   * @description
   * This endpoint will:
   * - Clear all org DNA fields (orgRootDnaId, orgChild1-4DnaId) by setting them to empty strings
   * - Use when an employee leaves their position (current_holderId becomes null)
   *
   * @param {request} request  Request body containing profileIds array and profileType
   */
  @Post("clear-org-dna")
  async clearOrgDna(
    @Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" },
  ) {
    const { profileIds, profileType } = request;

    // Validate profileIds
    if (!profileIds || profileIds.length === 0) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า");
    }

    // Validate profileType
    if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
      throw new HttpError(
        HttpStatus.BAD_REQUEST,
        "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
      );
    }

    const result = await this.keycloakAttributeService.clearOrgDnaAttributes(
      profileIds,
      profileType,
    );

    return new HttpSuccess({
      message: "Clear org DNA attributes เสร็จสิ้น",
      ...result,
    });
  }

  /**
   * Batch sync all users (Admin only)
   *
   * @summary Batch sync all users to Keycloak without limit (ADMIN)
   *
   * @description Syncs profileId and orgRootDnaId to Keycloak for all users
   * that have a keycloak ID. Uses parallel processing for better performance.
   *
   * Features:
   * - Resume from checkpoint after failures (use resume=true)
   * - Automatic retry with exponential backoff
   * - Rate limiting to avoid overwhelming Keycloak
   * - Progress tracking and persistence
   *
   * @param resume - Resume from last checkpoint (default: false)
   * @param maxRetries - Maximum retry attempts for failed operations (default: 3)
   * @param rateLimit - Requests per second rate limit (default: 10)
   * @param clearProgress - Clear existing progress and start fresh (default: false)
   */
  @Post("sync-all")
  async syncAll(
    @Query() resume: boolean = false,
    @Query() maxRetries: number = 3,
    @Query() rateLimit: number = 10,
    @Query() clearProgress: boolean = false,
  ) {
    const result = await this.keycloakAttributeService.batchSyncUsers({
      resume,
      maxRetries,
      rateLimit,
      clearProgress,
    });

    return new HttpSuccess({
      message: "Batch sync เสร็จสิ้น",
      total: result.total,
      success: result.success,
      failed: result.failed,
      details: result.details,
      resumed: result.resumed,
    });
  }

  /**
   * Ensure Keycloak users exist for all profiles (Admin only)
   *
   * @summary Create or verify Keycloak users for all profiles in Profile and ProfileEmployee tables (ADMIN)
   *
   * @description
   * This endpoint will:
   * - Create new Keycloak users for profiles without a keycloak ID
   * - Create new Keycloak users for profiles where the stored keycloak ID doesn't exist in Keycloak
   * - Verify existing Keycloak users
   * - Skip profiles without a citizenId
   */
  @Post("ensure-users")
  async ensureAllUsers() {
    const result = await this.keycloakAttributeService.batchEnsureKeycloakUsers();
    return new HttpSuccess({
      message: "Batch ensure Keycloak users เสร็จสิ้น",
      ...result,
    });
  }

  /**
   * Clear orphaned Keycloak users (Admin only)
   *
   * @summary Delete Keycloak users that are not in the database (ADMIN)
   *
   * @description
   * This endpoint will:
   * - Find users in Keycloak that are not referenced in Profile or ProfileEmployee tables
   * - Delete those orphaned users from Keycloak
   * - Skip protected users (super_admin, admin_issue)
   *
   * @param {request} request  Request body containing skipUsernames array
   */
  @Post("clear-orphaned-users")
  async clearOrphanedUsers(@Body() request?: { skipUsernames?: string[] }) {
    const skipUsernames = request?.skipUsernames || ["super_admin", "admin_issue"];
    const result = await this.keycloakAttributeService.clearOrphanedKeycloakUsers(skipUsernames);
    return new HttpSuccess({
      message: "Clear orphaned Keycloak users เสร็จสิ้น",
      ...result,
    });
  }

  /**
   * Sync profiles with missing empType for a specific month (Admin only)
   *
   * @summary Find profiles updated in specified month with missing empType in Keycloak and sync them (ADMIN)
   *
   * @description
   * This endpoint will:
   * - List profiles from Profile table where lastUpdatedAt falls within the specified month
   * - For each profile, check Keycloak if empType attribute is empty/null
   * - If empType is empty, sync the profile using existing sync logic
   * - Return summary of sync results
   *
   * Features:
   * - Dry run mode (dryRun=true) to check without syncing
   * - Configurable concurrency for parallel processing
   * - Rate limiting to avoid overwhelming Keycloak
   * - Detailed error reporting
   * - Idempotent (can be safely re-run)
   *
   * @param {request} request  Request body containing month parameter
   * @param dryRun - If true, only check without syncing (default: false)
   * @param concurrency - Number of parallel operations (default: 5)
   * @param rateLimit - Requests per second limit (default: 10)
   */
  @Post("sync-missing-emptype")
  @Response<HttpError>(HttpStatus.BAD_REQUEST, "Invalid month format")
  @Response<HttpError>(HttpStatus.INTERNAL_SERVER_ERROR, "Sync operation failed")
  async syncMissingEmpType(
    @Body() request: {
      month: string;
      profileType?: "PROFILE" | "PROFILE_EMPLOYEE";
    },
    @Query() dryRun: boolean = false,
    @Query() concurrency: number = 5,
    @Query() rateLimit: number = 10,
  ) {
    const { month, profileType = "PROFILE" } = request;

    // Validate month format (YYYY-MM)
    const monthRegex = /^\d{4}-\d{2}$/;
    if (!monthRegex.test(month)) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "รูปแบบเดือนไม่ถูกต้อง ต้องเป็น YYYY-MM");
    }

    // Validate profileType
    if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
      throw new HttpError(
        HttpStatus.BAD_REQUEST,
        "profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
      );
    }

    // Validate concurrency
    if (concurrency < 1 || concurrency > 20) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "concurrency ต้องอยู่ระหว่าง 1 ถึง 20");
    }

    // Validate rateLimit
    if (rateLimit < 1 || rateLimit > 50) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "rateLimit ต้องอยู่ระหว่าง 1 ถึง 50");
    }

    // Execute sync
    const result = await this.keycloakAttributeService.syncMissingEmpTypeByMonth({
      month,
      profileType,
      dryRun,
      concurrency,
      rateLimit,
    });

    return new HttpSuccess({
      message: `Sync ${dryRun ? "check " : ""}เสร็จสิ้น`,
      ...result,
    });
  }
}
