const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const database = require('../config/database');
const jwtConfig = require('../config/jwt');
const User = require('../models/user');
const { sendMail } = require('../utils/email');

class AuthService {
  // Register new user
  async register(userData) {
    const { username, email, password, first_name, last_name } = userData;

    // Validate input
    if (!username || !email || !password) {
      throw new Error('Username, email, and password are required');
    }

    if (!User.validateEmail(email)) {
      throw new Error('Invalid email format');
    }

    const passwordValidation = User.validatePassword(password);
    if (!passwordValidation.valid) {
      throw new Error(passwordValidation.message);
    }

    // Check if user already exists
    const existingUser = await User.findByEmail(email);
    if (existingUser) {
      throw new Error('User with this email already exists');
    }

    const existingUsername = await User.findByUsername(username);
    if (existingUsername) {
      throw new Error('Username already taken');
    }

    // Create user
    const newUser = await User.create({
      username,
      email,
      password,
      first_name,
      last_name
    });

    console.log(`👤 New user registered: ${newUser.email}`);

    // Send verification email (non-blocking but awaited to surface errors in dev)
    try {
      await this.sendVerificationEmail(newUser);
      console.log(`✅ Verification email sent successfully to ${newUser.email}`);
    } catch (err) {
      console.error('❌ Failed to send verification email:', {
        error: err.message,
        user: newUser.email,
        stack: err.stack
      });

      // In development, don't fail the registration if email fails
      if (process.env.NODE_ENV === 'development') {
        console.warn('⚠️ Registration completed but verification email failed. User can still login.');
      } else {
        // In production, this might be more critical
        console.error('🚨 Critical: Verification email failed in production environment');
      }
    }

    return newUser;
  }

  // Login user
  async login(credentials, sessionInfo = {}) {
    const { email, password } = credentials;
    const { ip_address, user_agent, device_info } = sessionInfo;

    if (!email || !password) {
      throw new Error('Email and password are required');
    }

    // Find user
    const user = await User.findByEmail(email);
    if (!user) {
      throw new Error('Invalid email or password');
    }

    // Require email to be verified before allowing login
    if (!user.email_verified) {
      throw new Error('Please verify your email before logging in');
    }

    // Verify password
    const isPasswordValid = await user.verifyPassword(password);
    if (!isPasswordValid) {
      throw new Error('Invalid email or password');
    }

    // Update last login
    await user.updateLastLogin();

    // Generate tokens
    const tokens = jwtConfig.generateTokenPair(user);

    // Store refresh token
    await this.storeRefreshToken(user.id, tokens.refreshToken);

    // Create session
    const session = await this.createSession(user.id, {
      ip_address,
      user_agent,
      device_info
    });

    console.log(`🔑 User logged in: ${user.email}`);

    return {
      user: user.toJSON(),
      tokens,
      session
    };
  }

  // ===============================
  // Email Verification
  // ===============================

  generateRandomToken() {
    return crypto.randomBytes(32).toString('hex');
  }

  hashDeterministic(value) {
    return crypto.createHash('sha256').update(value).digest('hex');
  }

  async createEmailVerificationToken(userId) {
    const rawToken = this.generateRandomToken();
    const tokenDigest = this.hashDeterministic(rawToken);
    const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
    const query = `
      INSERT INTO email_verification_tokens (user_id, token_hash, expires_at)
      VALUES ($1, $2, $3)
      RETURNING id
    `;
    await database.query(query, [userId, tokenDigest, expiresAt]);
    return rawToken;
  }

  async sendVerificationEmail(user) {
    const token = await this.createEmailVerificationToken(user.id);
    // Use centralized URL configuration - no environment variables needed
    let verifyUrl;
    try {
      // Load centralized config from repository root
      // eslint-disable-next-line global-require
      const urls = require('../../../../config/urls');
      if (urls && typeof urls.getVerificationUrl === 'function') {
        verifyUrl = urls.getVerificationUrl(token);
      } else if (urls && urls.FRONTEND_URL) {
        const FRONTEND_URL = urls.FRONTEND_URL.replace(/\/$/, '');
        verifyUrl = `${FRONTEND_URL}/verify-email?token=${encodeURIComponent(token)}`;
      } else {
        // Hardcoded fallback - use live dashboard domain
        verifyUrl = `https://dashboard.codenuk.com/verify-email?token=${encodeURIComponent(token)}`;
      }
    } catch (err) {
      // Hardcoded fallback - use live dashboard domain
      verifyUrl = `https://dashboard.codenuk.com/verify-email?token=${encodeURIComponent(token)}`;
    }

    console.log(`📧 Generated verification URL: ${verifyUrl}`);
    console.log(`📧 Using centralized URL config`);

    const today = new Date();
    const dateString = today.toLocaleDateString('en-US');

    const subject = 'Verify your email - Tech4biz';
    const text = `Hi ${user.first_name || user.username}, please verify your email by visiting: ${verifyUrl}`;
    const html = `
      <div style="font-family:Arial,Helvetica,sans-serif;max-width:640px;margin:0 auto;padding:24px;border:1px solid #eee;border-radius:8px;">
        <h2>👋 Welcome to Codenuk, ${user.first_name || user.username}!</h2>
        <p>We're excited to have you on board 🎉</p>
        <p>Hi <b>${user.first_name || user.username}</b>, thanks for registering with us on <b>${dateString}</b>.
        Please confirm your email address by clicking the button below:</p>
        <div style="text-align:center;margin:24px 0;">
          <table cellpadding="0" cellspacing="0" border="0" style="margin:0 auto;">
            <tr>
              <td style="background:#1677ff;border-radius:6px;padding:0;">
                <a href="${verifyUrl}" style="background:#1677ff;color:#fff;text-decoration:none;padding:12px 20px;border-radius:6px;display:block;font-weight:600;font-size:16px;">Verify Email</a>
              </td>
            </tr>
          </table>
        </div>
        <p>This link is valid for <b>24 hours</b>.</p>
        <p>If you didn't create this account, please ignore this email.</p>
        <p style="font-size:14px;color:#666;margin-top:20px;border-top:1px solid #eee;padding-top:15px;">
          <strong>Can't click the button?</strong> Copy and paste this link into your browser:<br>
          <span style="color:#1677ff;word-break:break-all;">${verifyUrl}</span>
        </p>
      </div>
    `;

    await sendMail(user.email, subject, text, html);
    console.log(`✉️ Verification email sent to ${user.email}`);
  }

  async verifyEmailToken(rawToken) {
    if (!rawToken) {
      throw new Error('Verification token is required');
    }
    const tokenDigest = this.hashDeterministic(rawToken);

    return await database.transaction(async (client) => {
      const findQuery = `
        SELECT * FROM email_verification_tokens 
        WHERE token_hash = $1 AND is_used = false AND expires_at > NOW()
        ORDER BY created_at DESC
        LIMIT 1
      `;
      const found = await client.query(findQuery, [tokenDigest]);
      if (found.rows.length === 0) {
        throw new Error('Invalid or expired verification link');
      }
      const record = found.rows[0];

      // Mark user as verified
      await client.query('UPDATE users SET email_verified = true, updated_at = NOW() WHERE id = $1', [record.user_id]);

      // Mark token as used
      await client.query('UPDATE email_verification_tokens SET is_used = true, used_at = NOW() WHERE id = $1', [record.id]);

      return { userId: record.user_id };
    });
  }

  // Refresh access token
  async refreshToken(refreshToken) {
    if (!refreshToken) {
      throw new Error('Refresh token is required');
    }

    // Verify refresh token
    let decoded;
    try {
      decoded = jwtConfig.verifyRefreshToken(refreshToken);
    } catch (error) {
      throw new Error('Invalid refresh token');
    }

    // Check if token exists and is not revoked (support deterministic + legacy bcrypt storage)
    const storedToken = await this.findStoredRefreshToken(decoded.userId, refreshToken);

    if (!storedToken || storedToken.is_revoked) {
      throw new Error('Refresh token is revoked or invalid');
    }

    if (new Date() > storedToken.expires_at) {
      throw new Error('Refresh token has expired');
    }

    // Get user
    const user = await User.findById(decoded.userId);
    if (!user) {
      throw new Error('User not found');
    }

    // Generate new tokens
    const tokens = jwtConfig.generateTokenPair(user);

    // Revoke old refresh token and store new one
    await this.revokeRefreshTokenById(storedToken.id);
    await this.storeRefreshToken(user.id, tokens.refreshToken);

    console.log(`🔄 Token refreshed for user: ${user.email}`);

    return {
      user: user.toJSON(),
      tokens
    };
  }

  // Logout user
  async logout(refreshToken, sessionToken = null) {
    if (refreshToken) {
      try {
        const decoded = jwtConfig.verifyRefreshToken(refreshToken);
        const storedToken = await this.findStoredRefreshToken(decoded.userId, refreshToken);
        if (storedToken) {
          await this.revokeRefreshTokenById(storedToken.id);
        }
      } catch (e) {
        console.warn('⚠️ Logout could not find refresh token to revoke:', e.message);
      }
    }

    if (sessionToken) {
      await this.endSession(sessionToken);
    }

    console.log('🚪 User logged out');
    return { message: 'Logged out successfully' };
  }

  // Store refresh token
  async storeRefreshToken(userId, refreshToken) {
    const tokenHash = this.hashDeterministic(refreshToken);
    const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
    const id = uuidv4();

    const query = `
      INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at)
      VALUES ($1, $2, $3, $4)
      RETURNING id
    `;

    const result = await database.query(query, [id, userId, tokenHash, expiresAt]);
    return result.rows[0];
  }

  // Get refresh token
  async getRefreshToken(tokenHash) {
    const query = `
      SELECT * FROM refresh_tokens 
      WHERE token_hash = $1
    `;

    const result = await database.query(query, [tokenHash]);
    return result.rows[0] || null;
  }

  // Revoke refresh token
  async revokeRefreshToken(tokenHash) {
    const query = `
      UPDATE refresh_tokens 
      SET is_revoked = true, revoked_at = NOW()
      WHERE token_hash = $1
    `;

    await database.query(query, [tokenHash]);
  }

  // Revoke refresh token by id (preferred)
  async revokeRefreshTokenById(id) {
    const query = `
      UPDATE refresh_tokens 
      SET is_revoked = true, revoked_at = NOW()
      WHERE id = $1
    `;
    await database.query(query, [id]);
  }

  // Create user session
  async createSession(userId, sessionInfo) {
    const sessionToken = uuidv4();
    const id = uuidv4();
    const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days

    const query = `
      INSERT INTO user_sessions (
        id, user_id, session_token, ip_address, user_agent, device_info, expires_at
      ) VALUES ($1, $2, $3, $4, $5, $6, $7)
      RETURNING *
    `;

    const values = [
      id,
      userId,
      sessionToken,
      sessionInfo.ip_address,
      sessionInfo.user_agent,
      sessionInfo.device_info ? JSON.stringify(sessionInfo.device_info) : null,
      expiresAt
    ];

    const result = await database.query(query, values);
    return result.rows[0];
  }

  // End session
  async endSession(sessionToken) {
    const query = `
      UPDATE user_sessions 
      SET is_active = false 
      WHERE session_token = $1
    `;

    await database.query(query, [sessionToken]);
  }

  // Update session activity
  async updateSessionActivity(sessionToken) {
    const query = `
      UPDATE user_sessions 
      SET last_activity = NOW() 
      WHERE session_token = $1 AND is_active = true
      RETURNING *
    `;

    const result = await database.query(query, [sessionToken]);
    return result.rows[0];
  }

  // Get user sessions
  async getUserSessions(userId) {
    const query = `
      SELECT * FROM user_sessions 
      WHERE user_id = $1 AND is_active = true
      ORDER BY last_activity DESC
    `;

    const result = await database.query(query, [userId]);
    return result.rows;
  }

  // Verify access token and get user
  async verifyAccessToken(token) {
    try {
      const decoded = jwtConfig.verifyAccessToken(token);
      const user = await User.findById(decoded.userId);

      if (!user) {
        throw new Error('User not found');
      }

      return user;
    } catch (error) {
      throw new Error('Invalid access token');
    }
  }

  // Hash token for storage
  async hashToken(token) {
    const saltRounds = 10;
    return await bcrypt.hash(token, saltRounds);
  }

  // Find stored refresh token using deterministic SHA-256 first, then legacy bcrypt compare
  async findStoredRefreshToken(userId, refreshToken) {
    const sha256 = this.hashDeterministic(refreshToken);
    // Try deterministic exact match
    let result = await database.query(
      `SELECT * FROM refresh_tokens WHERE token_hash = $1 LIMIT 1`,
      [sha256]
    );
    if (result.rows.length > 0) {
      return result.rows[0];
    }

    // Fallback: try to match legacy bcrypt-hashed tokens for this user
    const candidates = await database.query(
      `SELECT * FROM refresh_tokens 
       WHERE user_id = $1 AND is_revoked = false AND expires_at > NOW()
       ORDER BY created_at DESC LIMIT 100`,
      [userId]
    );
    for (const row of candidates.rows) {
      try {
        if (row.token_hash && row.token_hash.startsWith('$2')) { // bcrypt hashed
          const match = await bcrypt.compare(refreshToken, row.token_hash);
          if (match) {
            return row;
          }
        }
      } catch (err) {
        // ignore and continue
      }
    }
    return null;
  }

  // Cleanup expired tokens and sessions
  async cleanup() {
    console.log('🧹 Starting auth cleanup...');

    // Cleanup expired tokens
    const tokenResult = await database.query('SELECT cleanup_expired_tokens()');
    const deletedTokens = tokenResult.rows[0].cleanup_expired_tokens;

    // Cleanup inactive sessions
    const sessionResult = await database.query('SELECT cleanup_inactive_sessions()');
    const inactiveSessions = sessionResult.rows[0].cleanup_inactive_sessions;

    console.log(`🧹 Cleanup completed: ${deletedTokens} tokens, ${inactiveSessions} sessions`);

    return { deletedTokens, inactiveSessions };
  }

  // Change user password
  async changePassword(userId, currentPassword, newPassword) {
    const user = await User.findById(userId);
    if (!user) {
      throw new Error('User not found');
    }

    const passwordValidation = User.validatePassword(newPassword);
    if (!passwordValidation.valid) {
      throw new Error(passwordValidation.message);
    }

    await user.changePassword(currentPassword, newPassword);

    // Revoke all refresh tokens to force re-login
    await this.revokeAllUserTokens(userId);

    console.log(`🔒 Password changed for user: ${user.email}`);
    return { message: 'Password changed successfully' };
  }

  // Revoke all user tokens
  async revokeAllUserTokens(userId) {
    const query = `
      UPDATE refresh_tokens 
      SET is_revoked = true, revoked_at = NOW()
      WHERE user_id = $1 AND is_revoked = false
    `;

    await database.query(query, [userId]);
  }

  // Get auth statistics
  async getStats() {
    const query = `
      SELECT 
        (SELECT COUNT(*) FROM users WHERE is_active = true) as total_users,
        (SELECT COUNT(*) FROM refresh_tokens WHERE is_revoked = false) as active_tokens,
        (SELECT COUNT(*) FROM user_sessions WHERE is_active = true) as active_sessions,
        (SELECT COUNT(*) FROM users WHERE last_login > NOW() - INTERVAL '24 hours') as users_24h,
        (SELECT COUNT(*) FROM users WHERE created_at > NOW() - INTERVAL '7 days') as new_users_7d
    `;

    const result = await database.query(query);
    return result.rows[0];
  }
}

module.exports = new AuthService();