import { User as UserModel } from '../models/User';
import { Op } from 'sequelize';
import { SSOUserData } from '../types/auth.types'; // Use shared type
import axios from 'axios';
import logger from '../utils/logger';

// Using UserModel type directly - interface removed to avoid duplication

interface OktaUser {
  id: string;
  status: string;
  profile: {
    firstName?: string;
    lastName?: string;
    displayName?: string;
    email: string;
    login: string;
    department?: string;
    mobilePhone?: string;
    [key: string]: any; // Allow any additional profile fields
  };
}

/**
 * Extract full user data from Okta Users API response (centralized extraction)
 * This ensures consistent field mapping across all user creation/update operations
 */
function extractOktaUserData(oktaUserResponse: any): SSOUserData | null {
  try {
    const profile = oktaUserResponse.profile || {};

    const userData: SSOUserData = {
      oktaSub: oktaUserResponse.id || '',
      email: profile.email || profile.login || '',
      employeeId: profile.employeeID || profile.employeeId || profile.employee_id || undefined,
      firstName: profile.firstName || undefined,
      lastName: profile.lastName || undefined,
      displayName: profile.displayName || undefined,
      department: profile.department || undefined,
      designation: profile.title || profile.designation || undefined,
      phone: profile.mobilePhone || profile.phone || profile.phoneNumber || undefined,
      manager: profile.manager || undefined, // Manager name from Okta
      jobTitle: profile.title || undefined,
      postalAddress: profile.postalAddress || undefined,
      mobilePhone: profile.mobilePhone || undefined,
      secondEmail: profile.secondEmail || profile.second_email || undefined,
      adGroups: Array.isArray(profile.memberOf) ? profile.memberOf : undefined,
    };

    // Validate required fields
    if (!userData.oktaSub || !userData.email) {
      return null;
    }

    return userData;
  } catch (error) {
    return null;
  }
}

export class UserService {
  /**
   * Build a consistent user payload for create/update from SSO data.
   * @param isUpdate - If true, excludes email from payload (email should never be updated)
   */
  private buildUserPayload(ssoData: SSOUserData, existingRole?: string, isUpdate: boolean = false) {
    const now = new Date();
    const payload: any = {
      oktaSub: ssoData.oktaSub,
      employeeId: ssoData.employeeId || null,
      firstName: ssoData.firstName || null,
      lastName: ssoData.lastName || null,
      displayName: ssoData.displayName || null,
      department: ssoData.department || null,
      designation: ssoData.designation || null,
      phone: ssoData.phone || null,
      manager: ssoData.manager || null,
      jobTitle: ssoData.designation || ssoData.jobTitle || null,
      postalAddress: ssoData.postalAddress || null,
      mobilePhone: ssoData.mobilePhone || null,
      secondEmail: ssoData.secondEmail || null,
      adGroups: ssoData.adGroups || null,
      lastLogin: now,
      updatedAt: now,
      isActive: ssoData.isActive ?? true,
      role: (ssoData.role as any) || existingRole || 'USER',
    };

    // Only include email for new users (never update email for existing users)
    if (!isUpdate) {
      payload.email = ssoData.email;
    }

    return payload;
  }

  async createOrUpdateUser(ssoData: SSOUserData): Promise<UserModel> {
    // Validate required fields
    if (!ssoData.email || !ssoData.oktaSub) {
      throw new Error('Email and Okta sub are required');
    }

    // Check if user exists by email (primary identifier) or oktaSub
    const existingUser = await UserModel.findOne({
      where: {
        [Op.or]: [
          { email: ssoData.email },
          { oktaSub: ssoData.oktaSub }
        ]
      }
    });

    if (existingUser) {
      // Update existing user - DO NOT update email (crucial identifier)
      const updatePayload = this.buildUserPayload(ssoData, existingUser.role, true); // isUpdate = true

      await existingUser.update(updatePayload);

      return existingUser;
    } else {
      // Create new user - oktaSub is required, email is included
      const createPayload = this.buildUserPayload(ssoData, 'USER', false); // isUpdate = false

      const newUser = await UserModel.create(createPayload);

      return newUser;
    }
  }

  async getUserById(userId: string): Promise<UserModel | null> {
    return await UserModel.findByPk(userId);
  }

  async getUserByEmployeeId(employeeId: string): Promise<UserModel | null> {
    return await UserModel.findOne({ where: { employeeId } });
  }

  async getAllUsers(): Promise<UserModel[]> {
    return await UserModel.findAll({
      order: [['createdAt', 'DESC']]
    });
  }

  async searchUsers(query: string, limit: number = 10, excludeUserId?: string, source: 'local' | 'okta' | 'default' = 'default'): Promise<any[]> {
    const q = (query || '').trim();
    if (!q) {
      return [];
    }

    // Get the current user's email to exclude them from results
    let excludeEmail: string | undefined;
    if (excludeUserId) {
      try {
        const currentUser = await UserModel.findByPk(excludeUserId);
        if (currentUser) {
          excludeEmail = (currentUser as any).email?.toLowerCase();
        }
      } catch (err) {
        // Ignore error - filtering will still work by userId for local search
      }
    }

    // If source is strictly 'local', skip Okta and search DB directly
    if (source === 'local') {
      return await this.searchUsersLocal(q, limit, excludeUserId);
    }

    // Search Okta users
    try {
      const oktaDomain = process.env.OKTA_DOMAIN;
      const oktaApiToken = process.env.OKTA_API_TOKEN;

      if (!oktaDomain || !oktaApiToken) {
        return await this.searchUsersLocal(q, limit, excludeUserId);
      }

      const response = await axios.get(`${oktaDomain}/api/v1/users`, {
        params: { q, limit: Math.min(limit, 50) },
        headers: {
          'Authorization': `SSWS ${oktaApiToken}`,
          'Accept': 'application/json'
        },
        timeout: 5000
      });

      const oktaUsers: OktaUser[] = response.data || [];

      // Transform Okta users to our format
      return oktaUsers
        .filter(u => {
          // Filter out inactive users
          if (u.status !== 'ACTIVE') return false;

          // Filter out current user by Okta ID or email
          if (excludeUserId && u.id === excludeUserId) return false;
          if (excludeEmail) {
            const userEmail = (u.profile.email || u.profile.login || '').toLowerCase();
            if (userEmail === excludeEmail) return false;
          }

          return true;
        })
        .map(u => ({
          userId: u.id,
          oktaSub: u.id,
          email: u.profile.email || u.profile.login,
          displayName: u.profile.displayName || `${u.profile.firstName || ''} ${u.profile.lastName || ''}`.trim(),
          firstName: u.profile.firstName,
          lastName: u.profile.lastName,
          department: u.profile.department,
          phone: u.profile.mobilePhone,
          isActive: true
        }));
    } catch (error: any) {
      return await this.searchUsersLocal(q, limit, excludeUserId);
    }
  }

  /**
   * Fallback: Search users in local database
   */
  private async searchUsersLocal(query: string, limit: number = 10, excludeUserId?: string): Promise<UserModel[]> {
    const q = (query || '').trim();
    if (!q) {
      return [];
    }
    const like = `%${q}%`;
    const orConds = [
      { email: { [Op.iLike as any]: like } as any },
      { displayName: { [Op.iLike as any]: like } as any },
      { firstName: { [Op.iLike as any]: like } as any },
      { lastName: { [Op.iLike as any]: like } as any },
    ];
    const where: any = { [Op.or]: orConds };
    if (excludeUserId) {
      where.userId = { [Op.ne]: excludeUserId } as any;
    }
    return await UserModel.findAll({
      where,
      order: [['displayName', 'ASC']],
      limit: Math.min(Math.max(limit || 10, 1), 50),
    });
  }

  /**
   * Fetch a user directly from Okta by their Okta ID
   * Used when we have an Okta ID and need to get user details
   */
  async fetchUserFromOktaById(oktaId: string): Promise<OktaUser | null> {
    try {
      const oktaDomain = process.env.OKTA_DOMAIN;
      const oktaApiToken = process.env.OKTA_API_TOKEN;

      if (!oktaDomain || !oktaApiToken) {
        return null;
      }

      const response = await axios.get(`${oktaDomain}/api/v1/users/${oktaId}`, {
        headers: {
          'Authorization': `SSWS ${oktaApiToken}`,
          'Accept': 'application/json'
        },
        timeout: 5000
      });

      return response.data as OktaUser;
    } catch (error: any) {
      if (error.response?.status === 404) {
        // User not found in Okta
        return null;
      }
      throw error;
    }
  }

  /**
   * Fetch user from Okta by email and extract full profile data
   * Returns SSOUserData with all fields including manager, jobTitle, etc.
   */
  async fetchAndExtractOktaUserByEmail(email: string): Promise<SSOUserData | null> {
    try {
      const oktaDomain = process.env.OKTA_DOMAIN;
      const oktaApiToken = process.env.OKTA_API_TOKEN;

      if (!oktaDomain || !oktaApiToken) {
        return null;
      }

      // Try to fetch by email directly first (more reliable)
      try {
        const directResponse = await axios.get(`${oktaDomain}/api/v1/users/${encodeURIComponent(email)}`, {
          headers: {
            'Authorization': `SSWS ${oktaApiToken}`,
            'Accept': 'application/json'
          },
          timeout: 5000,
          validateStatus: (status) => status < 500
        });

        if (directResponse.status === 200 && directResponse.data) {
          return extractOktaUserData(directResponse.data);
        }
      } catch (directError) {
        // Fall through to search method
      }

      // Fallback: Search Okta users by email
      const response = await axios.get(`${oktaDomain}/api/v1/users`, {
        params: { search: `profile.email eq "${email}"`, limit: 1 },
        headers: {
          'Authorization': `SSWS ${oktaApiToken}`,
          'Accept': 'application/json'
        },
        timeout: 5000
      });

      const users: any[] = response.data || [];
      if (users.length > 0) {
        return extractOktaUserData(users[0]);
      }

      return null;
    } catch (error: any) {
      console.error(`Failed to fetch user from Okta by email ${email}:`, error.message);
      return null;
    }
  }

  /**
   * Search users in Okta by displayName
   * Uses Okta search API: /api/v1/users?search=profile.displayName eq "displayName"
   * @param displayName - Display name to search for
   * @returns Array of matching users from Okta
   */
  async searchOktaByDisplayName(displayName: string): Promise<OktaUser[]> {
    try {
      const oktaDomain = process.env.OKTA_DOMAIN;
      const oktaApiToken = process.env.OKTA_API_TOKEN;

      if (!oktaDomain || !oktaApiToken) {
        logger.warn('[UserService] Okta not configured, returning empty array for displayName search');
        return [];
      }

      // Search Okta users by displayName
      const response = await axios.get(`${oktaDomain}/api/v1/users`, {
        params: {
          search: `profile.displayName eq "${displayName}"`,
          limit: 50
        },
        headers: {
          'Authorization': `SSWS ${oktaApiToken}`,
          'Accept': 'application/json'
        },
        timeout: 5000
      });

      const oktaUsers: OktaUser[] = response.data || [];

      // Filter only active users
      return oktaUsers.filter(u => u.status === 'ACTIVE');
    } catch (error: any) {
      logger.error(`[UserService] Error searching Okta by displayName "${displayName}":`, error.message);
      return [];
    }
  }

  /**
   * Fetch user from Okta by email (legacy method, kept for backward compatibility)
   * @deprecated Use fetchAndExtractOktaUserByEmail instead for full profile extraction
   */
  async fetchUserFromOktaByEmail(email: string): Promise<OktaUser | null> {
    const userData = await this.fetchAndExtractOktaUserByEmail(email);
    if (!userData) return null;

    // Return in legacy format for backward compatibility
    return {
      id: userData.oktaSub,
      status: 'ACTIVE',
      profile: {
        email: userData.email,
        login: userData.email,
        firstName: userData.firstName,
        lastName: userData.lastName,
        displayName: userData.displayName,
        department: userData.department,
        mobilePhone: userData.mobilePhone,
      }
    };
  }

  /**
   * Ensure user exists in database (create if not exists)
   * Used when tagging users from Okta search results or when only email is provided
   * 
   * @param oktaUserData - Can be just { email } or full user data
   */
  async ensureUserExists(oktaUserData: {
    userId?: string;
    email: string;
    displayName?: string;
    firstName?: string;
    lastName?: string;
    department?: string;
    phone?: string;
    designation?: string;
    jobTitle?: string;
    manager?: string;
    employeeId?: string;
    employeeNumber?: string;
    secondEmail?: string;
    mobilePhone?: string;
    location?: string;
  }): Promise<UserModel> {
    const email = oktaUserData.email.toLowerCase();

    // Check if user already exists in database
    let user = await UserModel.findOne({
      where: {
        [Op.or]: [
          { email },
          ...(oktaUserData.userId ? [{ oktaSub: oktaUserData.userId }] : [])
        ]
      }
    });

    if (user) {
      // Update existing user with latest info from Okta (if provided)
      const updateData: any = {
        email,
        isActive: true,
        updatedAt: new Date()
      };

      if (oktaUserData.userId) updateData.oktaSub = oktaUserData.userId;
      if (oktaUserData.firstName) updateData.firstName = oktaUserData.firstName;
      if (oktaUserData.lastName) updateData.lastName = oktaUserData.lastName;
      if (oktaUserData.displayName) updateData.displayName = oktaUserData.displayName;
      if (oktaUserData.department) updateData.department = oktaUserData.department;
      if (oktaUserData.phone) updateData.phone = oktaUserData.phone;
      if (oktaUserData.designation) updateData.designation = oktaUserData.designation;
      if (oktaUserData.employeeId) updateData.employeeId = oktaUserData.employeeId;

      await user.update(updateData);
      return user;
    }

    // User not found in DB - try to fetch from Okta
    if (!oktaUserData.userId) {
      const oktaUser = await this.fetchUserFromOktaByEmail(email);
      if (oktaUser) {
        // Found in Okta - create with Okta data
        user = await UserModel.create({
          oktaSub: oktaUser.id,
          email,
          employeeId: null,
          firstName: oktaUser.profile.firstName || null,
          lastName: oktaUser.profile.lastName || null,
          displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim() || email.split('@')[0],
          department: oktaUser.profile.department || null,
          designation: null,
          phone: oktaUser.profile.mobilePhone || null,
          isActive: oktaUser.status === 'ACTIVE',
          role: 'USER',
          lastLogin: undefined,
          createdAt: new Date(),
          updatedAt: new Date()
        });
        return user;
      } else {
        // Not found in Okta either
        throw new Error(`User with email '${email}' not found in organization directory`);
      }
    }

    // Create new user with provided data
    user = await UserModel.create({
      oktaSub: oktaUserData.userId,
      email,
      employeeId: oktaUserData.employeeId || null,
      firstName: oktaUserData.firstName || null,
      lastName: oktaUserData.lastName || null,
      displayName: oktaUserData.displayName || email.split('@')[0],
      department: oktaUserData.department || null,
      designation: oktaUserData.designation || oktaUserData.jobTitle || null,
      phone: oktaUserData.phone || oktaUserData.mobilePhone || null,
      isActive: true,
      role: 'USER',
      lastLogin: undefined,
      createdAt: new Date(),
      updatedAt: new Date()
    });

    return user;
  }
}
