const ReferralTokenRepository = require('../../repositories/referral/ReferralTokenRepository');
const PersonalUserRepository = require('../../repositories/user/personal/PersonalUserRepository');
const CompanyUserRepository = require('../../repositories/user/company/CompanyUserRepository');
const UserStatusService = require('../status/UserStatusService');
const UserSettingsRepository = require('../../repositories/settings/UserSettingsRepository');
const MailService = require('../email/MailService');
const { logger } = require('../../middleware/logger');

class ReferralService {
  // --- Token creation and management ---
  static async createReferralToken({ userId, expiresInDays, maxUses, unitOfWork }) {
    logger.info('ReferralService:createReferralToken:start', { userId, expiresInDays, maxUses });

    const normalizeUnlimited = (val, label) => {
      if (val === undefined || val === null || val === '') return -1;
      if (typeof val === 'string') {
        const trimmed = val.trim().toLowerCase();
        if (['unlimited','∞','-1'].includes(trimmed)) return -1;
        const num = Number(val);
        if (!Number.isNaN(num)) val = num;
      }
      // Treat 0 as unlimited (frontend currently sends 0 for unlimited)
      if (val === 0) {
        logger.debug(`ReferralService:createReferralToken:interpreting_0_as_unlimited`, { field: label });
        return -1;
      }
      return val;
    };

    expiresInDays = normalizeUnlimited(expiresInDays, 'expiresInDays');
    maxUses = normalizeUnlimited(maxUses, 'maxUses');

    if (!(expiresInDays === -1 || (Number.isInteger(expiresInDays) && expiresInDays >= 1 && expiresInDays <= 7))) {
      throw new Error('Expiry must be between 1 and 7 days or unlimited');
    }
    if (!(maxUses === -1 || (Number.isInteger(maxUses) && maxUses >= 1))) {
      throw new Error('maxUses must be a positive integer or unlimited');
    }

    const unlimited = maxUses === -1;
    if (unlimited) logger.info('ReferralService:createReferralToken:unlimited', { userId });

    const UNLIMITED_EXPIRY_DATE = new Date('2999-12-31T23:59:59Z');
    const expiresAt = (expiresInDays === -1)
      ? UNLIMITED_EXPIRY_DATE
      : new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000);

    const repo = new ReferralTokenRepository(unitOfWork);
    const token = await repo.createToken({
      createdByUserId: userId,
      expiresAt,
      maxUses
    });

    logger.info('ReferralService:createReferralToken:success', {
      token: token.token,
      unlimited,
      storedMaxUses: token.maxUses,
      storedUsesRemaining: token.usesRemaining
    });

    return this.presentToken({
      id: token.id,
      token: token.token,
      created_by_user_id: token.createdByUserId,
      expires_at: token.expiresAt,
      status: token.status,
      max_uses: token.maxUses,
      uses_remaining: token.usesRemaining
    });
  }

  static async getUserReferralTokens(userId, unitOfWork) {
    logger.info('ReferralService:getUserReferralTokens:start', { userId });
    const repo = new ReferralTokenRepository(unitOfWork);
    const rows = await repo.getTokensByUser(userId);

    const base = process.env.REFERRAL_PUBLIC_BASE_URL || 'https://profit-planet.partners/register?ref=';

    const tokens = rows.map(r => {
      const max = r.max_uses;
      const remaining = r.uses_remaining;
      const isUnlimited = (max === -1) || (remaining === -1);
      let used;

      if (isUnlimited) {
        used = 0;
      } else if (typeof r.used_count === 'number') {
        used = r.used_count;
      } else if (Number.isFinite(max) && Number.isFinite(remaining)) {
        used = max - remaining;
      } else {
        used = 0;
      }

      if (!Number.isFinite(used) || used < 0) used = 0;
      if (!isUnlimited && used > max) used = max;

      const maxUsesCanonical = isUnlimited ? null : max;
      const usage = isUnlimited ? 'unlimited' : `${used}/${maxUsesCanonical}`;

      return {
        id: r.id,
        token: r.token,
        link: `${base}${r.token}`,
        status: r.status,
        created: r.created_at ? new Date(r.created_at).toISOString() : null,
        expires: isUnlimited ? null : (r.expires_at ? new Date(r.expires_at).toISOString() : null),
        used,
        maxUses: maxUsesCanonical,
        isUnlimited,
        usage,
        progress: usage,
        // Aliases (defensive)
        useCount: used,
        usedCount: used
      };
    });

    tokens.slice(0, 5).forEach(t => {
      logger.debug('ReferralService:getUserReferralTokens:canonical', {
        id: t.id, usage: t.usage, used: t.used, maxUses: t.maxUses, isUnlimited: t.isUnlimited
      });
    });

    logger.info('ReferralService:getUserReferralTokens:success', { count: tokens.length });
    return tokens;
  }

  static async getUserReferralStats(userId, unitOfWork) {
    logger.info('ReferralService:getUserReferralStats:start', { userId });
    const repo = new ReferralTokenRepository(unitOfWork);
    const stats = await repo.getStatsByUser(userId);
    logger.info('ReferralService:getUserReferralStats:success', { stats });
    // Pass through all fields including new ones
    return stats;
  }

  static async deactivateReferralToken(userId, tokenId, unitOfWork) {
    logger.info('ReferralService:deactivateReferralToken:start', { userId, tokenId });
    const repo = new ReferralTokenRepository(unitOfWork);
    await repo.deactivateToken(tokenId, userId);
    logger.info('ReferralService:deactivateReferralToken:success', { userId, tokenId });
  }

  static async countActiveReferralTokens(userId, unitOfWork) {
    logger.info('ReferralService:countActiveReferralTokens:start', { userId });
    const repo = new ReferralTokenRepository(unitOfWork);
    const count = await repo.countActiveTokensByUser(userId);
    logger.info('ReferralService:countActiveReferralTokens:success', { userId, count });
    return count;
  }

  static presentToken(row) {
    if (!row) return row;

    // Raw DB fields
    const maxNum = (row.max_uses !== undefined ? row.max_uses : row.maxUses);
    const remNum = (row.uses_remaining !== undefined ? row.uses_remaining : row.usesRemaining);
    const createdRaw = row.created_at || row.createdAt;
    const expiresRaw = row.expires_at || row.expiresAt;

    const createdDate = createdRaw ? new Date(createdRaw) : null;
    const createdValid = createdDate && !isNaN(createdDate.getTime());
    const createdISO = createdValid ? createdDate.toISOString() : null;

    const expiresDate = expiresRaw ? new Date(expiresRaw) : null;
    const expiresValid = expiresDate && !isNaN(expiresDate.getTime());

    const unlimited = (maxNum === -1) || (remNum === -1);
    const maxDisplay = row.max_uses_display || row.max_uses_label || (unlimited ? 'unlimited' : String(maxNum));
    const remDisplay = row.uses_remaining_display || row.uses_remaining_label || (unlimited ? 'unlimited' : String(remNum));

    // Numeric total & used
    let totalCount = unlimited ? -1 : (
      typeof row.total_count === 'number'
        ? row.total_count
        : (Number.isFinite(maxNum) ? maxNum : 0)
    );

    let usedCount = unlimited ? 0 : (
      typeof row.used_count === 'number'
        ? row.used_count
        : (Number.isFinite(maxNum) && Number.isFinite(remNum) ? (maxNum - remNum) : 0)
    );

    if (!unlimited) {
      if (!Number.isFinite(usedCount) || usedCount < 0) usedCount = 0;
      if (Number.isFinite(totalCount) && usedCount > totalCount) usedCount = totalCount;
    }

    const remainingCount = unlimited
      ? -1
      : (Number.isFinite(remNum)
          ? remNum
          : (Number.isFinite(totalCount) ? (totalCount - usedCount) : 0));

    // Build usage string deterministically (do NOT trust usage_display if present)
    const usageString = unlimited
      ? 'unlimited'
      : (totalCount > 0 ? `${usedCount}/${totalCount}` : '0/0');

    const infinitySymbol = '∞';
    const denominatorDisplay = unlimited ? infinitySymbol : String(totalCount);
    const numeratorDisplay = unlimited ? '0' : String(usedCount);

    // Expiry display (Never for unlimited OR far future year >= 2999)
    const expiresAtISO = unlimited
      ? null
      : (expiresValid ? expiresDate.toISOString() : null);
    const expiresAtDisplay = unlimited
      ? 'Never'
      : (expiresValid ? expiresDate.toISOString() : 'Unknown');

    // Numeric maxUses for FE (for unlimited keep -1 sentinel)
    const maxUsesNumber = unlimited ? -1 : (Number.isFinite(totalCount) ? totalCount : 0);

    // NEW: derive referrer identity (email & name) early so it can be inserted into tokenObj
    const referrerEmailDerived =
      row.referrer_email ||
      row.referrerEmail ||
      row.email ||
      row.created_by_email ||
      row.createdByEmail ||
      null;

    // Try to build a display name if first/last name fields exist (silent if not)
    const refFirst = row.referrer_first_name || row.first_name || row.firstName;
    const refLast = row.referrer_last_name || row.last_name || row.lastName;
    let referrerNameDerived = row.referrer_name || row.referrerName || null;
    if (!referrerNameDerived && (refFirst || refLast)) {
      referrerNameDerived = [refFirst, refLast].filter(Boolean).join(' ').trim() || null;
    }
    if (!referrerNameDerived) {
      // fallback to email for display if no name
      referrerNameDerived = referrerEmailDerived || null;
    }

    const tokenObj = {
      id: row.id,
      token: row.token,
      status: row.status,

      // Core time fields
      createdAt: createdISO,
      created_at: createdISO,
      createdAtISO: createdISO,
      createdAtEpoch: createdValid ? createdDate.getTime() : null,

      expiresAt: expiresAtISO,
      expires_at: expiresAtISO,
      expiresAtISO,
      expiresAtEpoch: (expiresAtISO ? expiresDate.getTime() : null),
      expiresAtDisplay,

      // Display & numeric usage
      usage: usageString,
      usageDisplay: usageString,
      progress: usageString,

      // Display values
      maxUsesDisplay: unlimited ? 'unlimited' : String(maxUsesNumber),
      usesRemainingDisplay: unlimited ? 'unlimited' : String(remainingCount),

      // Primary numeric values (camelCase)
      maxUses: maxUsesNumber,
      usesRemaining: remainingCount,
      used: usedCount,
      isUnlimited: unlimited,

      // Raw helpers
      maxUsesRaw: maxUsesNumber,
      usesRemainingRaw: remainingCount,
      usesUsedRaw: usedCount,

      // Additional alias set to satisfy unknown FE expectations
      limit: maxUsesNumber,
      total: maxUsesNumber,
      totalCount: maxUsesNumber,
      max: maxUsesNumber,
      capacity: maxUsesNumber,
      remaining: remainingCount,
      remainingCount,
      left: remainingCount,
      usedCount,
      currentUses: usedCount,
      progressUsed: usedCount,
      totalUses: maxUsesNumber,
      remainingUses: remainingCount,
      usedUses: usedCount,

      // Snake_case (legacy)
      max_uses: maxUsesNumber,
      uses_remaining: remainingCount,

      // Displays for numerator/denominator parts
      numeratorDisplay,
      denominatorDisplay,
      infinitySymbol,

      // Structured usage object
      usageData: {
        used: usedCount,
        remaining: remainingCount,
        max: maxUsesNumber,
        limit: maxUsesNumber,
        total: maxUsesNumber,
        isUnlimited: unlimited,
        display: usageString,
        numerator: numeratorDisplay,
        denominator: denominatorDisplay
      },

      // Keep original creation if present (for FE that lists columns generically)
      created_by_user_id: row.created_by_user_id || row.createdByUserId,
      createdByUserId: row.created_by_user_id || row.createdByUserId,

      // Count of individual usage rows
      usageCount: row.usage_count,

      // NEW: referrer identity fields (added near end to avoid interfering with existing logic)
      referrerEmail: referrerEmailDerived,
      referrerName: referrerNameDerived,
      // legacy / alias fields some FE might probe
      ownerEmail: referrerEmailDerived,
      createdByEmail: referrerEmailDerived,
      userEmail: referrerEmailDerived
    };

    if (!tokenObj.referrerEmail) {
      logger.debug('ReferralService:presentToken:referrer_email_missing', { tokenId: row.id, hasRawField: !!row.referrer_email });
    }

    return tokenObj;
  }

  // --- Registration via referral ---
  static evaluateTokenRecord(rec) {
    if (!rec) return { valid: false, reason: 'not_found' };

    // Defensive normalization
    const rawMax = (rec.max_uses === null || rec.max_uses === undefined) ? -1 : rec.max_uses;
    const rawRemaining = (rec.uses_remaining === null || rec.uses_remaining === undefined) ? -1 : rec.uses_remaining;

    const isUnlimited = (rawMax === -1) || (rawRemaining === -1);

    // Diagnostics
    logger.debug('ReferralService:evaluateTokenRecord:raw', {
      status: rec.status,
      expires_at: rec.expires_at,
      max_uses: rawMax,
      uses_remaining: rawRemaining,
      isUnlimited
    });

    if (rec.status !== 'active') return { valid: false, reason: 'inactive' };

    const now = Date.now();
    const expMs = rec.expires_at ? new Date(rec.expires_at).getTime() : NaN;

    if (!isUnlimited) {
      if (isNaN(expMs) || expMs <= now) return { valid: false, reason: 'expired' };
      if (rawRemaining === 0) return { valid: false, reason: 'exhausted' };
      if (rawRemaining < 0) {
        // Inconsistent negative (not -1 sentinel) -> treat as internal issue; allow but log
        logger.warn('ReferralService:evaluateTokenRecord:negative_remaining_non_unlimited', {
          uses_remaining: rawRemaining, max_uses: rawMax
        });
      }
    }

    return {
      valid: true,
      reason: null,
      isUnlimited,
      usesRemaining: isUnlimited ? null : rawRemaining
    };
  }

  static async getReferrerInfo(token, unitOfWork) {
    logger.info('ReferralService:getReferrerInfo:start', { token });
    const repo = new ReferralTokenRepository(unitOfWork);
    const raw = await repo.getReferrerInfoByToken(token);
    if (!raw) {
      // Diagnostic: check if token exists at all (without JOIN)
      try {
        const conn = unitOfWork.connection;
        const [diag] = await conn.query(
          'SELECT id, token, status, created_by_user_id, expires_at, max_uses, uses_remaining FROM referral_tokens WHERE token = ? LIMIT 1',
          [token]
        );
        if (diag.length) {
          logger.warn('ReferralService:getReferrerInfo:token_exists_but_join_failed', {
            token, tokenId: diag[0].id, created_by_user_id: diag[0].created_by_user_id,
            status: diag[0].status, expires_at: diag[0].expires_at
          });
        } else {
          logger.warn('ReferralService:getReferrerInfo:token_truly_not_in_db', { token });
        }
      } catch (diagErr) {
        logger.warn('ReferralService:getReferrerInfo:diagnostic_query_failed', { error: diagErr.message });
      }
      return { valid: false, reason: 'not_found' };
    }
    const evalResult = this.evaluateTokenRecord(raw);
    if (!evalResult.valid) {
      logger.warn('ReferralService:getReferrerInfo:invalid', {
        token, reason: evalResult.reason, max_uses: raw.max_uses, uses_remaining: raw.uses_remaining
      });
      return { valid: false, reason: evalResult.reason };
    }

    const normalized = this.presentToken(raw);

    const refEmail = normalized.referrerEmail || raw.referrer_email || raw.email || null;
    const refName = normalized.referrerName || refEmail;

    if (!refEmail) {
      logger.warn('ReferralService:getReferrerInfo:referrer_email_unresolved', { token, tokenId: raw.id });
    } else {
      logger.debug('ReferralService:getReferrerInfo:referrer_email_resolved', { token, refEmail });
    }

    return {
      valid: true,
      referrerId: raw.referrer_id || normalized.referrerId || null,
      referrerName: refName,
      referrerEmail: refEmail,
      isUnlimited: normalized.isUnlimited,
      usesRemaining: normalized.isUnlimited ? -1 : normalized.usesRemaining,
      maxUses: normalized.maxUses,
      usage: normalized.usage
    };
  }

  static async registerPersonalWithReferral(registrationData, refToken, unitOfWork) {
    logger.info('ReferralService:registerPersonalWithReferral:start', { refToken });
    const repo = new ReferralTokenRepository(unitOfWork);
    const raw = await repo.getReferrerInfoByToken(refToken);
    const evalResult = this.evaluateTokenRecord(raw);
    if (!evalResult.valid) {
      logger.warn('ReferralService:registerPersonalWithReferral:token_invalid', {
        refToken, reason: evalResult.reason, max_uses: raw && raw.max_uses, uses_remaining: raw && raw.uses_remaining
      });
      throw new Error(evalResult.reason || 'invalid_token');
    }

    const personalRepo = new PersonalUserRepository(unitOfWork);
    const user = await personalRepo.create(registrationData);

    await UserStatusService.initializeUserStatus(user.id, 'personal', unitOfWork, 'pending');

    if (UserSettingsRepository) {
      const settingsRepo = new UserSettingsRepository(unitOfWork);
      await settingsRepo.createDefaultSettings(user.id, unitOfWork);
    } else if (unitOfWork.connection) {
      await unitOfWork.connection.query(`
        INSERT INTO user_settings (user_id) VALUES (?) 
        ON DUPLICATE KEY UPDATE user_id = user_id
      `, [user.id]);
    }

    await repo.markReferralTokenUsed(raw.token_id, user.id, unitOfWork);

    await MailService.sendRegistrationEmail({
      email: registrationData.email,
      firstName: registrationData.firstName,
      lastName: registrationData.lastName,
      userType: 'personal'
    });

    logger.info('ReferralService:registerPersonalWithReferral:success', { userId: user.id, email: user.email });
    return user;
  }

  static async registerCompanyWithReferral(registrationData, refToken, unitOfWork) {
    logger.info('ReferralService:registerCompanyWithReferral:start', { refToken });
    const repo = new ReferralTokenRepository(unitOfWork);
    const raw = await repo.getReferrerInfoByToken(refToken);
    const evalResult = this.evaluateTokenRecord(raw);
    if (!evalResult.valid) {
      logger.warn('ReferralService:registerCompanyWithReferral:token_invalid', {
        refToken, reason: evalResult.reason, max_uses: raw && raw.max_uses, uses_remaining: raw && raw.uses_remaining
      });
      throw new Error(evalResult.reason || 'invalid_token');
    }

    const companyRepo = new CompanyUserRepository(unitOfWork);
    const { companyEmail, password, companyName, companyPhone, contactPersonName, contactPersonPhone } = registrationData;
    const user = await companyRepo.create({
      companyEmail,
      password,
      companyName,
      companyPhone,
      contactPersonName,
      contactPersonPhone
    });

    await UserStatusService.initializeUserStatus(user.id, 'company', unitOfWork, 'pending');

    if (UserSettingsRepository) {
      const settingsRepo = new UserSettingsRepository(unitOfWork);
      await settingsRepo.createDefaultSettings(user.id, unitOfWork);
    } else if (unitOfWork.connection) {
      await unitOfWork.connection.query(`
        INSERT INTO user_settings (user_id) VALUES (?) 
        ON DUPLICATE KEY UPDATE user_id = user_id
      `, [user.id]);
    }

    await repo.markReferralTokenUsed(raw.token_id, user.id, unitOfWork);

    await MailService.sendRegistrationEmail({
      email: registrationData.companyEmail,
      companyName: registrationData.companyName,
      userType: 'company'
    });

    logger.info('ReferralService:registerCompanyWithReferral:success', { userId: user.id, email: user.email });
    return user;
  }

  // --- Referral usage processing ---
  static async processReferral(userId, referralEmail, unitOfWork) {
    logger.info('ReferralService:processReferral:start', { userId, referralEmail });
    try {
      const referrer = await this.findReferrerByEmail(referralEmail, unitOfWork);
      if (!referrer) {
        logger.warn('ReferralService:processReferral:referrer_not_found', { referralEmail });
        return false;
      }

      const referralToken = await this.findActiveReferralToken(referrer.id, unitOfWork);
      if (!referralToken) {
        logger.warn('ReferralService:processReferral:no_active_token', { referrerId: referrer.id });
        return false;
      }

      await this.createReferralUsage(referralToken.id, userId, unitOfWork);
      await this.updateTokenUsage(referralToken.id, unitOfWork);

      logger.info('ReferralService:processReferral:success', { userId, referralTokenId: referralToken.id });
      return true;
    } catch (error) {
      logger.error('ReferralService:processReferral:error', { userId, referralEmail, error });
      return false;
    }
  }

  static async findReferrerByEmail(email, unitOfWork) {
    logger.debug('ReferralService:findReferrerByEmail', { email });
    const conn = unitOfWork.connection;
    const query = `SELECT id FROM users WHERE email = ?`;
    const [rows] = await conn.query(query, [email]);
    return rows.length > 0 ? rows[0] : null;
  }

  static async findActiveReferralToken(userId, unitOfWork) {
    logger.debug('ReferralService:findActiveReferralToken', { userId });
    const conn = unitOfWork.connection;
    const query = `
      SELECT * FROM referral_tokens 
      WHERE created_by_user_id = ? AND status = 'active' AND expires_at > NOW()
      LIMIT 1
    `;
    const [rows] = await conn.query(query, [userId]);
    return rows.length > 0 ? rows[0] : null;
  }

  static async createReferralUsage(tokenId, userId, unitOfWork) {
    logger.debug('ReferralService:createReferralUsage', { tokenId, userId });
    const conn = unitOfWork.connection;
    const query = `
      INSERT INTO referral_token_usage (referral_token_id, used_by_user_id)
      VALUES (?, ?)
    `;
    await conn.query(query, [tokenId, userId]);
  }

  static async updateTokenUsage(tokenId, unitOfWork) {
    logger.debug('ReferralService:updateTokenUsage', { tokenId });
    const conn = unitOfWork.connection;
    const query = `
      UPDATE referral_tokens 
      SET uses_remaining = uses_remaining - 1
      WHERE id = ? AND uses_remaining > 0
    `;
    await conn.query(query, [tokenId]);
  }

  static async getReferredUsers(userId, unitOfWork) {
    logger.info('ReferralService:getReferredUsers:start', { userId });
    const repo = new ReferralTokenRepository(unitOfWork);
    const rows = await repo.getReferredUsersByCreator(userId);

    const users = rows.map(r => {
      let registeredAt = null;
      if (r.registered_at) {
        const d = new Date(r.registered_at);
        registeredAt = isNaN(d.getTime()) ? null : d.toISOString();
      }
      return {
        id: r.id,
        name: r.name || r.email,
        email: r.email,
        type: r.user_type,
        registeredAt,
        status: r.status || null
      };
    });

    const result = {
      total_referred_users: users.length,
      users
    };
    logger.info('ReferralService:getReferredUsers:success', { userId, total: result.total_referred_users });
    return result;
  }
}

module.exports = ReferralService;
