import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { ulid } from 'ulid';
import { query } from '../db/connection.js';

const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production';
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'your-super-secret-refresh-key-change-in-production';

const BCRYPT_ROUNDS = 12;
const ACCESS_TOKEN_EXPIRES_IN = '15m';
const REFRESH_TOKEN_EXPIRES_IN = '7d';

export interface User {
  id: string;
  email: string;
  display_name: string;
  avatar_url?: string;
  created_at: string;
  updated_at: string;
}

export interface AuthTokens {
  accessToken: string;
  refreshToken: string;
}

export interface AuthResult {
  user: User;
  tokens: AuthTokens;
}

class AuthService {
  async register(email: string, password: string, displayName: string): Promise<AuthResult> {
    const { rows: existing } = await query('SELECT id FROM users WHERE email = $1', [email]);
    if (existing.length > 0) throw new Error('EMAIL_EXISTS');

    const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);
    const userId = ulid();

    await query(
      'INSERT INTO users (id, email, password_hash, display_name) VALUES ($1, $2, $3, $4)',
      [userId, email, passwordHash, displayName]
    );

    const { rows } = await query(
      'SELECT id, email, display_name, avatar_url, created_at, updated_at FROM users WHERE id = $1',
      [userId]
    );
    const user = rows[0] as User;
    const tokens = this.generateTokens(user);
    return { user, tokens };
  }

  async login(email: string, password: string): Promise<AuthResult> {
    const { rows } = await query(
      'SELECT id, email, password_hash, display_name, avatar_url, created_at, updated_at FROM users WHERE email = $1',
      [email]
    );
    if (rows.length === 0) throw new Error('INVALID_CREDENTIALS');

    const userWithPassword = rows[0];
    const passwordValid = await bcrypt.compare(password, userWithPassword.password_hash);
    if (!passwordValid) throw new Error('INVALID_CREDENTIALS');

    const { password_hash, ...user } = userWithPassword;
    const tokens = this.generateTokens(user);
    return { user, tokens };
  }

  async getProfile(userId: string): Promise<User | null> {
    const { rows } = await query(
      'SELECT id, email, display_name, avatar_url, created_at, updated_at FROM users WHERE id = $1',
      [userId]
    );
    return rows[0] || null;
  }

  async updateProfile(userId: string, data: { display_name?: string; avatar_url?: string }): Promise<User> {
    const updates: string[] = [];
    const values: any[] = [];
    let idx = 1;

    if (data.display_name !== undefined) { updates.push(`display_name = $${idx}`); values.push(data.display_name); idx++; }
    if (data.avatar_url !== undefined) { updates.push(`avatar_url = $${idx}`); values.push(data.avatar_url); idx++; }
    if (updates.length === 0) throw new Error('NO_UPDATES_PROVIDED');

    values.push(userId);
    await query(`UPDATE users SET ${updates.join(', ')} WHERE id = $${idx}`, values);

    const user = await this.getProfile(userId);
    if (!user) throw new Error('USER_NOT_FOUND');
    return user;
  }

  async changePassword(userId: string, currentPassword: string, newPassword: string): Promise<boolean> {
    const { rows } = await query('SELECT password_hash FROM users WHERE id = $1', [userId]);
    if (rows.length === 0) throw new Error('USER_NOT_FOUND');

    const valid = await bcrypt.compare(currentPassword, rows[0].password_hash);
    if (!valid) throw new Error('INVALID_CURRENT_PASSWORD');

    const newHash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS);
    await query('UPDATE users SET password_hash = $1 WHERE id = $2', [newHash, userId]);
    return true;
  }

  async refreshTokens(refreshToken: string): Promise<AuthResult> {
    try {
      const decoded = jwt.verify(refreshToken, JWT_REFRESH_SECRET) as any;
      const user = await this.getProfile(decoded.sub);
      if (!user) throw new Error('USER_NOT_FOUND');
      const tokens = this.generateTokens(user);
      return { user, tokens };
    } catch (error) {
      throw new Error('INVALID_REFRESH_TOKEN');
    }
  }

  verifyAccessToken(token: string): any {
    try {
      return jwt.verify(token, JWT_SECRET);
    } catch (error) {
      throw new Error('INVALID_ACCESS_TOKEN');
    }
  }

  private generateTokens(user: User): AuthTokens {
    const payload = { sub: user.id, email: user.email, display_name: user.display_name };
    const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: ACCESS_TOKEN_EXPIRES_IN });
    const refreshToken = jwt.sign({ sub: user.id }, JWT_REFRESH_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRES_IN });
    return { accessToken, refreshToken };
  }
}

export const authService = new AuthService();
