const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const database = require('../config/database');

/**
 * OAuth Service
 * Verwaltet OAuth/SSO-Logins (Google, GitHub, etc.)
 */
class OAuthService {
  constructor() {
    this.jwtSecret = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
    this.jwtExpiration = process.env.JWT_EXPIRATION || '24h';
  }

  /**
   * OAuth-Login/Registrierung
   * @param {Object} profile - OAuth-Provider-Profil
   * @param {string} provider - Provider-Name (google, github, etc.)
   * @returns {Promise<Object>} Token und Benutzer-Info
   */
  async authenticateWithProvider(profile, provider) {
    return this.completeOAuthLogin(profile, provider);
  }

  async completeOAuthLogin(profile, provider, options = {}) {
    const { User, AuthInfo, AuthIdentity, AuthToken } = database.getModels();
    
    const providerId = profile.id;
    const email = profile.emails && profile.emails[0] ? profile.emails[0].value : null;
    const displayName = profile.displayName || profile.name || email;

    if (!providerId) {
      throw new Error('OAuth Provider-ID fehlt');
    }

    // Prüfen ob OAuth-Identity bereits existiert
    let authIdentity = await AuthIdentity.findOne({
      where: {
        provider,
        identity: providerId
      },
      include: [{
        model: database.getModels().AuthInfo,
        as: 'authInfo',
        include: [{
          model: User,
          as: 'user'
        }]
      }]
    });

    let user, authInfo;

    if (authIdentity && authIdentity.authInfo) {
      // Bestehender OAuth-Benutzer
      authInfo = authIdentity.authInfo;
      user = authInfo.user;
      if (options.linkUserId && Number(user.id) !== Number(options.linkUserId)) {
        throw new Error('Dieser Google-Account ist bereits mit einem anderen Benutzer verknüpft');
      }
    } else {
      if (options.linkUserId) {
        authInfo = await AuthInfo.findOne({
          where: { user_id: options.linkUserId },
          include: [{ model: User, as: 'user' }]
        });

        if (!authInfo) {
          throw new Error('Benutzer für Verknüpfung nicht gefunden');
        }

        authIdentity = await AuthIdentity.create({
          auth_info_id: authInfo.id,
          provider,
          identity: providerId,
          version: 0
        });
        user = authInfo.user;
      } else {
        // Neuer OAuth-Benutzer oder explizite Verknüpfung mit bestehendem Account

        // Prüfen ob Benutzer mit dieser E-Mail bereits existiert
        if (email) {
          authInfo = await AuthInfo.findOne({
            where: { email },
            include: [{
              model: User,
              as: 'user'
            }]
          });
        }

        if (authInfo) {
          return {
            requiresLink: true,
            pendingToken: this.createPendingToken(profile, provider),
            email,
            displayName
          };
        } else {
          // Neuen Benutzer erstellen
          user = await User.create({
            full_name: displayName,
            role: 0,
            daily_hours: 8,
            week_hours: 40,
            week_workdays: 5,
            preferred_title_type: 0,
            version: 0,
            last_change: new Date()
          });

          // Auth-Info erstellen (ohne Passwort für OAuth-only Accounts)
          authInfo = await AuthInfo.create({
            user_id: user.id,
            email: email || `${provider}_${providerId}@oauth.local`,
            password_hash: '', // Kein Passwort für OAuth-only
            password_method: 'oauth',
            password_salt: '',
            status: 1,
            failed_login_attempts: 0,
            email_token: '',
            email_token_role: 0,
            unverified_email: '',
            version: 0
          });

          // OAuth-Identity erstellen
          authIdentity = await AuthIdentity.create({
            auth_info_id: authInfo.id,
            provider,
            identity: providerId,
            version: 0
          });
        }
      }
    }

    return this.createLoginResult(user, authInfo, provider);
  }

  createPendingToken(profile, provider) {
    return jwt.sign(
      {
        provider,
        providerId: profile.id,
        email: profile.emails && profile.emails[0] ? profile.emails[0].value : null,
        displayName: profile.displayName || profile.name || null,
        type: 'oauth-pending'
      },
      this.jwtSecret,
      { expiresIn: '15m' }
    );
  }

  verifyPendingToken(pendingToken) {
    const data = jwt.verify(pendingToken, this.jwtSecret);
    if (data.type !== 'oauth-pending' || !data.provider || !data.providerId) {
      throw new Error('Ungültiger OAuth-Verknüpfungstoken');
    }
    return data;
  }

  createStateToken(data) {
    return jwt.sign({ ...data, type: 'oauth-state' }, this.jwtSecret, { expiresIn: '15m' });
  }

  verifyStateToken(stateToken) {
    if (!stateToken) return {};
    const data = jwt.verify(stateToken, this.jwtSecret);
    if (data.type !== 'oauth-state') {
      throw new Error('Ungültiger OAuth-State');
    }
    return data;
  }

  async linkPendingToPasswordAccount(pendingToken, email, password) {
    const authService = require('./AuthService');
    const pending = this.verifyPendingToken(pendingToken);
    const loginResult = await authService.login(email, password, '0');
    const profile = {
      id: pending.providerId,
      displayName: pending.displayName || loginResult.user.full_name,
      emails: pending.email ? [{ value: pending.email }] : []
    };
    return this.completeOAuthLogin(profile, pending.provider, { linkUserId: loginResult.user.id });
  }

  async linkPendingToAuthenticatedUser(pendingToken, userId) {
    const pending = this.verifyPendingToken(pendingToken);
    const profile = {
      id: pending.providerId,
      displayName: pending.displayName || null,
      emails: pending.email ? [{ value: pending.email }] : []
    };
    return this.completeOAuthLogin(profile, pending.provider, { linkUserId: userId });
  }

  createLoginResult(user, authInfo, provider) {
    const { AuthToken } = database.getModels();

    // JWT Token generieren
    const token = jwt.sign(
      {
        userId: user.id,
        email: authInfo.email,
        role: user.role,
        provider
      },
      this.jwtSecret,
      { expiresIn: this.jwtExpiration }
    );

    // Token in Datenbank speichern
    const expiresAt = new Date();
    expiresAt.setHours(expiresAt.getHours() + 24);

    return AuthToken.create({
      auth_info_id: authInfo.id,
      value: crypto.createHash('sha256').update(token).digest('hex'),
      expires: expiresAt,
      version: 0
    }).then(() => ({
      token,
      user: {
        id: user.id,
        full_name: user.full_name,
        email: authInfo.email,
        role: user.role,
        provider
      }
    }));
  }

  /**
   * OAuth-Identity für Benutzer abrufen
   * @param {number} userId - Benutzer-ID
   * @returns {Promise<Array>} Liste verknüpfter OAuth-Provider
   */
  async getUserIdentities(userId) {
    const { AuthInfo, AuthIdentity } = database.getModels();

    const authInfo = await AuthInfo.findOne({
      where: { user_id: userId },
      include: [{
        model: AuthIdentity,
        as: 'identities'
      }]
    });

    if (!authInfo || !authInfo.identities) {
      return [];
    }

    return authInfo.identities.map(identity => ({
      provider: identity.provider,
      identity: identity.identity,
      id: identity.id
    }));
  }

  /**
   * OAuth-Identity entfernen
   * @param {number} userId - Benutzer-ID
   * @param {string} provider - Provider-Name
   * @returns {Promise<boolean>} Erfolg
   */
  async unlinkProvider(userId, provider) {
    const { AuthInfo, AuthIdentity } = database.getModels();

    const authInfo = await AuthInfo.findOne({
      where: { user_id: userId }
    });

    if (!authInfo) {
      throw new Error('Benutzer nicht gefunden');
    }

    const deleted = await AuthIdentity.destroy({
      where: {
        auth_info_id: authInfo.id,
        provider
      }
    });

    return deleted > 0;
  }
}

module.exports = new OAuthService();

