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

// 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;
  };
}

export class UserService {
  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 }
        ]
      }
    });

    const now = new Date();

    if (existingUser) {
      // Update existing user - include oktaSub to ensure it's synced
      await existingUser.update({
        email: ssoData.email,
        oktaSub: ssoData.oktaSub,
        employeeId: ssoData.employeeId || null, // Optional
        firstName: ssoData.firstName || null,
        lastName: ssoData.lastName || null,
        displayName: ssoData.displayName || null,
        department: ssoData.department || null,
        designation: ssoData.designation || null,
        phone: ssoData.phone || null,
        // location: (ssoData as any).location || null, // Ignored for now - schema not finalized
        lastLogin: now,
        updatedAt: now,
        isActive: true, // Ensure user is active after SSO login
      });
      
      return existingUser;
    } else {
      // Create new user - oktaSub is required
      const newUser = await UserModel.create({
        email: ssoData.email,
        oktaSub: ssoData.oktaSub, // Required
        employeeId: ssoData.employeeId || null, // Optional
        firstName: ssoData.firstName || null,
        lastName: ssoData.lastName || null,
        displayName: ssoData.displayName || null,
        department: ssoData.department || null,
        designation: ssoData.designation || null,
        phone: ssoData.phone || null,
        // location: (ssoData as any).location || null, // Ignored for now - schema not finalized
        isActive: true,
        role: 'USER', // Default role for new users
        lastLogin: now
      });

      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): 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
      }
    }

    // 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;
    }
  }

  /**
   * Ensure user exists in database (create if not exists)
   * Used when tagging users from Okta search results
   */
  async ensureUserExists(oktaUserData: {
    userId: string;
    email: string;
    displayName?: string;
    firstName?: string;
    lastName?: string;
    department?: string;
    phone?: string;
  }): Promise<UserModel> {
    const email = oktaUserData.email.toLowerCase();
    
    // Check if user already exists
    let user = await UserModel.findOne({
      where: {
        [Op.or]: [
          { email },
          { oktaSub: oktaUserData.userId }
        ]
      }
    });

    if (user) {
      // Update existing user with latest info from Okta
      await user.update({
        oktaSub: oktaUserData.userId,
        email,
        firstName: oktaUserData.firstName || user.firstName,
        lastName: oktaUserData.lastName || user.lastName,
        displayName: oktaUserData.displayName || user.displayName,
        department: oktaUserData.department || user.department,
        phone: oktaUserData.phone || user.phone,
        isActive: true,
        updatedAt: new Date()
      });
      return user;
    }

    // Create new user
    user = await UserModel.create({
      oktaSub: oktaUserData.userId,
      email,
      employeeId: null, // Will be updated on first login
      firstName: oktaUserData.firstName || null,
      lastName: oktaUserData.lastName || null,
      displayName: oktaUserData.displayName || email.split('@')[0],
      department: oktaUserData.department || null,
      designation: null,
      phone: oktaUserData.phone || null,
      isActive: true,
      role: 'USER',
      lastLogin: undefined, // Not logged in yet, just created for tagging
      createdAt: new Date(),
      updatedAt: new Date()
    });

    return user;
  }
}
