/**
 * TMS API Service
 * Handles all TMS API calls for permission management
 * Enhanced with user search and project member filtering
 */

import axios from 'axios';
import { config } from '../config';
import { authService } from './auth.service';
import { logger } from '../utils/logger';
import type { ProjectDetails, Subset, PermissionType, UserSearchResult } from '../types';

// Language code to TMS locale mapping
const LANG_TO_LOCALE: Record<string, string> = {
  US: 'en_US',
  FR: 'fr_FR',
  ES: 'es_ES',
  DE: 'de_DE',
  TW: 'zh_TW',
  JP: 'ja_JP',
  TH: 'th_TH',
  KR: 'ko_KR',
  KO: 'ko_KR',
};

/**
 * Convert language code to TMS locale
 * @throws Error if lang is not in the mapping
 */
export function langToLocale(lang?: string): string {
  if (!lang) {
    throw new Error('Language code is required for TMS API operations');
  }
  const upperLang = lang.toUpperCase();
  const locale = LANG_TO_LOCALE[upperLang];
  if (!locale) {
    throw new Error(`Unsupported language code: ${lang}. Supported: ${Object.keys(LANG_TO_LOCALE).join(', ')}`);
  }
  return locale;
}

class TmsApiService {
  private static instance: TmsApiService;

  private constructor() { }

  public static getInstance(): TmsApiService {
    if (!TmsApiService.instance) {
      TmsApiService.instance = new TmsApiService();
    }
    return TmsApiService.instance;
  }

  /**
   * Helper: Execute API call with automatic auth retry on failure
   * Detects auth expiration from response and retries once with fresh login
   */
  private async withAuthRetry<T = any>(apiCall: () => Promise<T>): Promise<T> {
    let response = await apiCall();

    // Check if response indicates auth failure (r=false or unauthorized)
    const hasAuthFailure = (response as any)?.data?.r === false || (response as any)?.status === 401;

    if (hasAuthFailure) {
      logger.warn('Auth expired or failed, refreshing credentials and retrying');
      authService.clearCache();
      await authService.login(true); // Force fresh login

      // Retry the API call with new credentials
      response = await apiCall();
    }

    return response;
  }

  /**
   * Get project details including members and subsets for a specific locale
   * @param projectId - TMS project ID
   * @param lang - Language code (e.g., "us", "de") - REQUIRED to get correct locale data
   * @throws Error with specific message for "Page Not Found" (project doesn't exist)
   */
  public async getProjectDetails(projectId: number, lang: string): Promise<ProjectDetails> {
    const locale = langToLocale(lang);
    const url = `${config.tmsApiUrl}/api/project/${projectId}`;

    try {
      logger.debug('Fetching project details', { projectId, lang, locale });

      // Execute with auto retry on auth failure
      const response = await this.withAuthRetry(async () => {
        const headers = await authService.getHeaders();
        return axios.get(url, { headers });
      });

      const data = response.data;

      // Check for API error response (project not found, access denied, etc.)
      if (!data.r) {
        const errorMsg = data.err ? JSON.stringify(data.err) : 'Unknown error';
        // Check for common "not found" indicators
        if (errorMsg.toLowerCase().includes('not found') ||
          errorMsg.toLowerCase().includes('does not exist') ||
          errorMsg.includes('404')) {
          throw new Error(`Project ${projectId} không tồn tại trên TMS`);
        }
        throw new Error(`TMS API error: ${errorMsg}`);
      }

      if (!data.payload || !data.payload.data) {
        throw new Error('Invalid project data structure');
      }

      // Extract locale data - MUST use the specific locale, not just first one
      const locales = Object.keys(data.payload.data);

      if (locales.length === 0) {
        throw new Error(`Project ${projectId} không có dữ liệu locale`);
      }

      // Find the requested locale in the response
      if (!data.payload.data[locale]) {
        // Check if locale exists in searchTitle (meaning it exists but user has no access)
        const searchTitle = data.payload.searchTitle || '';
        // searchTitle format varies, usually contains locale codes or names
        // Simple check if the locale code is present in the searchTitle string/object
        const searchTitleStr = typeof searchTitle === 'string' ? searchTitle : JSON.stringify(searchTitle);

        if (searchTitleStr.includes(locale) || searchTitleStr.includes(lang.toUpperCase())) {
          logger.warn('Locale exists in searchTitle but not in data (Access Denied)', {
            projectId,
            locale,
            searchTitle: searchTitleStr
          });
          throw new Error(`LOCALE_EXISTS_NO_ACCESS: ${projectId} ${locale}`);
        }

        logger.warn('Requested locale not found in project', {
          projectId,
          requestedLocale: locale,
          availableLocales: locales,
          searchTitle: searchTitleStr
        });
        throw new Error(`Project ${projectId} không có ngôn ngữ ${lang.toUpperCase()} (${locale}). Có sẵn: ${locales.join(', ')}`);
      }

      const projectData = data.payload.data[locale];

      if (!projectData || !projectData.workspace) {
        throw new Error('Project workspace not found');
      }

      // Map subsets
      const subsets: Subset[] = projectData.workspace.map((ws: any) => ({
        id: ws.id,
        title: ws.title,
        lang: ws.lang,
        members: ws.members || {},
        episodes: ws.episodes || {},
      }));

      logger.debug('Project details fetched', {
        projectId,
        locale,
        subsetsCount: subsets.length,
        membersCount: Object.keys(projectData.members || {}).length,
      });

      return {
        projectId,
        name: projectData.name,
        subsets,
        members: projectData.members || {},
      };
    } catch (error: any) {
      // Handle HTTP 404 specifically
      if (error.response?.status === 404) {
        const notFoundError = new Error(`Project ${projectId} không tồn tại trên TMS (404 Not Found)`);
        logger.error('Project not found', { projectId });
        throw notFoundError;
      }

      logger.error('Error fetching project details', {
        projectId,
        status: error.response?.status,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Add member to project (grants R permission to all subsets by default)
   */
  public async addProjectMember(projectId: number, email: string, lang?: string): Promise<boolean> {
    const url = `${config.tmsApiUrl}/api/project/${projectId}/member`;
    const locale = langToLocale(lang);
    const payload = {
      locale,
      email: email,
      isSupervisor: false,
    };

    try {
      logger.debug('Adding project member', { projectId, email, locale });

      const response = await this.withAuthRetry(async () => {
        const headers = await authService.getHeaders();
        return axios.post(url, payload, { headers });
      });

      if (response.data && response.data.r === true) {
        logger.debug('✅ Project member added', { projectId, email });
        return true;
      } else {
        const errorMsg = response.data?.err ? JSON.stringify(response.data.err) : 'Unknown error';
        throw new Error(`API returned r:false - ${errorMsg}`);
      }
    } catch (error: any) {
      logger.error('Error adding project member', {
        projectId,
        email,
        status: error.response?.status,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Grant subset permission (R or RW)
   */
  public async grantSubsetPermission(
    projectId: number,
    subsetId: number,
    email: string,
    type: PermissionType,
    lang?: string
  ): Promise<boolean> {
    const url = `${config.tmsApiUrl}/api/project/${projectId}/subset/${subsetId}/member`;
    const locale = langToLocale(lang);
    const payload = {
      locale,
      email: email,
      type: type,
    };

    try {
      logger.debug('Granting subset permission', {
        projectId,
        subsetId,
        email,
        type,
        locale,
      });

      const response = await this.withAuthRetry(async () => {
        const headers = await authService.getHeaders();
        return axios.patch(url, payload, { headers });
      });

      if (response.data && response.data.r === true) {
        logger.debug('✅ Subset permission granted', {
          projectId,
          subsetId,
          email,
          type,
        });
        return true;
      } else {
        const errorMsg = response.data?.err ? JSON.stringify(response.data.err) : 'Unknown error';
        throw new Error(`API returned r:false - ${errorMsg}`);
      }
    } catch (error: any) {
      logger.error('Error granting subset permission', {
        projectId,
        subsetId,
        email,
        type,
        status: error.response?.status,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Delete project member
   */
  public async deleteProjectMember(projectId: number, email: string, lang?: string): Promise<boolean> {
    const url = `${config.tmsApiUrl}/api/project/${projectId}/member`;
    const locale = langToLocale(lang);
    const payload = {
      locale,
      email: email,
    };

    try {
      logger.debug('Deleting project member', { projectId, email, locale });

      const response = await this.withAuthRetry(async () => {
        const headers = await authService.getHeaders();
        return axios.delete(url, {
          headers,
          data: payload,
        });
      });

      if (response.data && response.data.r === true) {
        logger.debug('✅ Project member deleted', { projectId, email });
        return true;
      } else {
        const errorMsg = response.data?.err ? JSON.stringify(response.data.err) : 'Unknown error';
        throw new Error(`API returned r:false - ${errorMsg}`);
      }
    } catch (error: any) {
      logger.error('Error deleting project member', {
        projectId,
        email,
        status: error.response?.status,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Search users in TMS
   * Enhanced: Filter out users already in specified project
   * @param excludeProjectLang - Required if excludeProjectId is provided (to get correct locale data)
   */
  public async searchUser(
    query: string,
    excludeProjectId?: number,
    excludeProjectLang?: string
  ): Promise<UserSearchResult[]> {
    const url = `${config.tmsApiUrl}/api/user/search`;
    const payload = {
      query: query,
    };

    try {
      logger.debug('Searching users', { query, excludeProjectId });

      const response = await this.withAuthRetry(async () => {
        const headers = await authService.getHeaders();
        return axios.post(url, payload, { headers });
      });

      if (response.data && response.data.r === true && response.data.payload) {
        let results: UserSearchResult[] = response.data.payload;

        // Filter out project members if excludeProjectId is provided
        if (excludeProjectId && excludeProjectLang) {
          try {
            const projectDetails = await this.getProjectDetails(excludeProjectId, excludeProjectLang);
            const existingEmails = new Set(Object.keys(projectDetails.members));

            results = results.filter(user => !existingEmails.has(user.email));

            logger.debug('Filtered search results', {
              query,
              totalResults: response.data.payload.length,
              filteredResults: results.length,
              excludedCount: response.data.payload.length - results.length,
            });
          } catch (error: any) {
            logger.warn('Failed to filter project members', {
              excludeProjectId,
              error: error.message,
            });
            // Return unfiltered results if project fetch fails
          }
        }

        return results;
      } else {
        return [];
      }
    } catch (error: any) {
      logger.error('Error searching users', {
        query,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Resolve username (display name) to email address
   * Searches for exact match of display name and returns corresponding email
   * @param username Display name like "DKI_공통_Shyob(L)"
   * @returns Email address for the user
   * @throws Error if user not found
   */
  public async resolveUsernameToEmail(username: string): Promise<string> {
    try {
      logger.debug('Resolving username to email', { username });

      // Search for the username
      const results = await this.searchUser(username);

      // Find exact match by name
      const exactMatch = results.find(user => user.name === username);

      if (exactMatch) {
        logger.debug('Username resolved to email', {
          username,
          email: exactMatch.email,
        });
        return exactMatch.email;
      }

      // If no exact match, throw error
      throw new Error(`User not found: ${username}`);
    } catch (error: any) {
      logger.error('Error resolving username to email', {
        username,
        error: error.message,
      });
      throw error;
    }
  }

  /**
   * Resolve multiple emails to usernames (display names)
   * Searches each email and returns a mapping of email -> username
   * @param emails Array of email addresses
   * @returns Map of email -> username (or email if not found)
   */
  public async resolveEmailsToUsernames(emails: string[]): Promise<Record<string, string>> {
    const result: Record<string, string> = {};

    // Process in batches to avoid rate limiting
    for (const email of emails) {
      try {
        // Search by email - TMS search can match email too
        const searchResults = await this.searchUser(email);

        // Find exact email match
        const match = searchResults.find(user => user.email === email);

        if (match) {
          result[email] = match.name;
        } else {
          // If no match found, use email as fallback
          result[email] = email;
        }

        // Small delay between requests
        await new Promise(resolve => setTimeout(resolve, 100));
      } catch (error: any) {
        logger.warn('Failed to resolve email to username', { email, error: error.message });
        result[email] = email; // Fallback to email
      }
    }

    logger.debug('Resolved emails to usernames', {
      totalEmails: emails.length,
      resolvedCount: Object.values(result).filter(v => v !== result[v]).length
    });

    return result;
  }

  /**
   * Get project details with automatic access granting
   * If the locale exists but user has no access, it automatically adds the system user and retries
   */
  public async getProjectDetailsWithAutoAccess(projectId: number, lang: string): Promise<ProjectDetails> {
    try {
      return await this.getProjectDetails(projectId, lang);
    } catch (error: any) {
      if (error.message && error.message.includes('LOCALE_EXISTS_NO_ACCESS')) {
        logger.debug('Locale exists but access denied. Attempting to grant access...', { projectId, lang });

        // Add system user to project
        try {
          // config.tmsEmail is the system user
          await this.addProjectMember(projectId, config.tmsEmail, lang);

          // Retry fetching details
          logger.debug('Access granted. Retrying fetch project details...', { projectId, lang });
          return await this.getProjectDetails(projectId, lang);
        } catch (addError: any) {
          logger.error('Failed to auto-grant access', {
            projectId,
            lang,
            error: addError.message
          });
          throw new Error(`Failed to auto-grant access for ${lang}: ${addError.message}`);
        }
      }
      throw error;
    }
  }
}

export const tmsApiService = TmsApiService.getInstance();
