import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import sequelize from '../database.js';
import User from '../models/User.js';
import UserToken from '../models/UserToken.js';
import UserClub from '../models/UserClub.js';
import Log from '../models/Log.js';
import ApiLog from '../models/ApiLog.js';
import MyTischtennis from '../models/MyTischtennis.js';
import MyTischtennisUpdateHistory from '../models/MyTischtennisUpdateHistory.js';
import MyTischtennisFetchLog from '../models/MyTischtennisFetchLog.js';
import ClickTtAccount from '../models/ClickTtAccount.js';
import BillingUserSetting from '../models/BillingUserSetting.js';
import BillingTemplate from '../models/BillingTemplate.js';
import BillingRun from '../models/BillingRun.js';
import crypto from 'crypto';
import { sendActivationEmail, sendPasswordResetEmail } from './emailService.js';

import { devLog, errorLog } from '../utils/logger.js';

const register = async (email, password) => {
  try {
    console.log('register', email, password);
    const activationCode = Math.random().toString(36).substring(2, 15);
    console.log('activationCode', activationCode);
    await User.create({ email, password, activationCode });
    console.log('User created');

    // Aktivierungs‑E-Mail versenden
    await sendActivationEmail(email, activationCode);
    console.log('Activation email sent');
    // Aufrufer bekommt absichtlich KEINE Userdaten zurück (siehe Controller)
    return;
  } catch (error) {
    // 1) Immer Grund im Log mit Kontext ausgeben (auch in Prod)
    errorLog('[authService.register] Fehler bei Registrierung', {
      name: error?.name,
      message: error?.message,
      code: error?.code,
      responseCode: error?.responseCode,
      response: error?.response,
    });
    // Optional zusätzlich ausführlich im Dev-Log
    devLog('[authService.register] Stacktrace:', error?.stack);

    // E-Mail existiert bereits
    if (error.name === 'SequelizeUniqueConstraintError') {
      const err = new Error('E-Mail-Adresse wird bereits verwendet');
      err.status = 409;
      throw err;
    }

    // Fehler beim Mailversand (nodemailer o.ä.) klarer kommunizieren
    if (error && (error.code || error.response || error.responseCode)) {
      const err = new Error('Registrierung fehlgeschlagen: E-Mail-Versand nicht möglich. Bitte Administrator kontaktieren.');
      err.status = 500;
      throw err;
    }

    const err = new Error('Registrierung fehlgeschlagen');
    err.status = 400;
    throw err;
  }
};

const activateUser = async (activationCode) => {
  const user = await User.findOne({ where: { activationCode } });
  if (!user) {
    const err = new Error('Aktivierungscode ungültig');
    err.status = 404;
    throw err;
  }
  user.isActive = true;
  user.activationCode = null;
  await user.save();
  return user;
};

const login = async (email, password, options = {}) => {
  if (!email || !password) {
    const err = new Error('Email und Passwort sind erforderlich.');
    err.status = 400;
    throw err;
  }
  const user = await User.findOne({ where: { email } });
  const validPassword = user && await bcrypt.compare(password, user.password);
  if (!validPassword) {
    const err = new Error('Ungültige Anmeldedaten');
    err.status = 401;
    throw err;
  }
  if (!user.isActive) {
    const err = new Error('Account wurde noch nicht aktiviert');
    err.status = 403;
    throw err;
  }
  const rememberMe = Boolean(options.rememberMe);
  const expiresInMs = rememberMe ? 90 * 24 * 3600 * 1000 : 3 * 3600 * 1000;
  const expiresIn = rememberMe ? '90d' : '3h';
  const expiresAt = new Date(Date.now() + expiresInMs);
  const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn });
  await UserToken.create({
    userId: user.id,
    token,
    expiresAt,
  });
  return { token, expiresAt: expiresAt.toISOString() };
};

const logout = async (token) => {
  if (!token) {
    const err = new Error('Token fehlt');
    err.status = 400;
    throw err;
  }
  await UserToken.destroy({ where: { token } });
  return { message: 'Logout erfolgreich' };
};

const deleteOwnAccount = async (userId, password) => {
  if (!userId || !password) {
    const err = new Error('Passwort ist erforderlich');
    err.status = 400;
    throw err;
  }

  const user = await User.findByPk(userId);
  const validPassword = user && await bcrypt.compare(password, user.password);
  if (!validPassword) {
    const err = new Error('Ungültiges Passwort');
    err.status = 401;
    throw err;
  }

  await sequelize.transaction(async (transaction) => {
    await UserToken.destroy({ where: { userId }, transaction });
    await UserClub.destroy({ where: { userId }, transaction });
    await Log.destroy({ where: { userId }, transaction });
    await ApiLog.update({ userId: null }, { where: { userId }, transaction });
    await MyTischtennis.destroy({ where: { userId }, transaction });
    await MyTischtennisUpdateHistory.destroy({ where: { userId }, transaction });
    await MyTischtennisFetchLog.destroy({ where: { userId }, transaction });
    await ClickTtAccount.destroy({ where: { userId }, transaction });
    await BillingUserSetting.destroy({ where: { userId }, transaction });
    await BillingTemplate.update({ createdByUserId: null }, { where: { createdByUserId: userId }, transaction });
    await BillingRun.update(
      {
        createdByUserId: null,
        selfRecipientName: 'Gelöschter Benutzer',
        iban: null,
      },
      { where: { selfRecipientUserId: userId }, transaction },
    );
    await BillingRun.update({ createdByUserId: null }, { where: { createdByUserId: userId }, transaction });
    await user.destroy({ transaction });
  });

  return { success: true };
};

const requestPasswordReset = async (email) => {
  if (!email) {
    const err = new Error('E-Mail-Adresse ist erforderlich.');
    err.status = 400;
    throw err;
  }

  const user = await User.findOne({ where: { email } });

  // Aus Sicherheitsgründen IMMER Erfolg melden (verhindert E-Mail-Enumeration)
  if (!user) {
    return { message: 'Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.' };
  }

  // Token erzeugen (kryptographisch sicher)
  const resetToken = crypto.randomBytes(32).toString('hex');
  const resetTokenExpires = new Date(Date.now() + 60 * 60 * 1000); // 1 Stunde gültig

  user.resetToken = resetToken;
  user.resetTokenExpires = resetTokenExpires;
  await user.save();

  try {
    await sendPasswordResetEmail(email, resetToken);
  } catch (mailError) {
    errorLog('[authService.requestPasswordReset] Fehler beim E-Mail-Versand', {
      message: mailError?.message,
      code: mailError?.code,
    });
    const err = new Error('E-Mail konnte nicht gesendet werden. Bitte Administrator kontaktieren.');
    err.status = 500;
    throw err;
  }

  return { message: 'Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.' };
};

const resetPassword = async (token, newPassword) => {
  if (!token || !newPassword) {
    const err = new Error('Token und neues Passwort sind erforderlich.');
    err.status = 400;
    throw err;
  }

  if (newPassword.length < 6) {
    const err = new Error('Das Passwort muss mindestens 6 Zeichen lang sein.');
    err.status = 400;
    throw err;
  }

  const { Op } = await import('sequelize');
  const user = await User.findOne({
    where: {
      resetToken: token,
      resetTokenExpires: { [Op.gt]: new Date() }
    }
  });

  if (!user) {
    const err = new Error('Der Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.');
    err.status = 400;
    throw err;
  }

  // Neues Passwort setzen (wird automatisch durch beforeUpdate-Hook gehasht)
  user.password = newPassword;
  user.resetToken = null;
  user.resetTokenExpires = null;
  await user.save();

  // Alle bestehenden Tokens des Users löschen (erzwingt Neuanmeldung)
  await UserToken.destroy({ where: { userId: user.id } });

  return { message: 'Passwort wurde erfolgreich geändert.' };
};

export { register, activateUser, login, logout, deleteOwnAccount, requestPasswordReset, resetPassword };
