/**
 * @ai-summary User profile data access layer
 * @ai-context Provides parameterized SQL queries for user profile operations
 */

import { Pool } from 'pg';
import {
  UserProfile,
  UserWithAdminStatus,
  ListUsersQuery,
  ListUsersResponse,
  SubscriptionTier,
} from '../domain/user-profile.types';
import { logger } from '../../../core/logging/logger';

// Base columns for user profile queries
const USER_PROFILE_COLUMNS = `
  id, auth0_sub, email, display_name, notification_email,
  subscription_tier, email_verified, onboarding_completed_at,
  deactivated_at, deactivated_by, deletion_requested_at, deletion_scheduled_for,
  created_at, updated_at
`;

export class UserProfileRepository {
  constructor(private pool: Pool) {}

  async getByAuth0Sub(auth0Sub: string): Promise<UserProfile | null> {
    const query = `
      SELECT ${USER_PROFILE_COLUMNS}
      FROM user_profiles
      WHERE auth0_sub = $1
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      if (result.rows.length === 0) {
        return null;
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error fetching user profile by auth0_sub', { error, auth0Sub });
      throw error;
    }
  }

  async getByEmail(email: string): Promise<UserProfile | null> {
    const query = `
      SELECT ${USER_PROFILE_COLUMNS}
      FROM user_profiles
      WHERE email = $1
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [email]);
      if (result.rows.length === 0) {
        return null;
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error fetching user profile by email', { error });
      throw error;
    }
  }

  async create(
    auth0Sub: string,
    email: string,
    displayName?: string
  ): Promise<UserProfile> {
    const query = `
      INSERT INTO user_profiles (auth0_sub, email, display_name, subscription_tier)
      VALUES ($1, $2, $3, 'free')
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [
        auth0Sub,
        email,
        displayName || null,
      ]);

      if (result.rows.length === 0) {
        throw new Error('Failed to create user profile');
      }

      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error creating user profile', { error, auth0Sub, email });
      throw error;
    }
  }

  async update(
    auth0Sub: string,
    updates: { displayName?: string; notificationEmail?: string }
  ): Promise<UserProfile> {
    const setClauses: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (updates.displayName !== undefined) {
      setClauses.push(`display_name = $${paramIndex++}`);
      values.push(updates.displayName);
    }

    if (updates.notificationEmail !== undefined) {
      setClauses.push(`notification_email = $${paramIndex++}`);
      values.push(updates.notificationEmail);
    }

    if (setClauses.length === 0) {
      throw new Error('No fields to update');
    }

    values.push(auth0Sub);

    const query = `
      UPDATE user_profiles
      SET ${setClauses.join(', ')}
      WHERE auth0_sub = $${paramIndex}
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, values);

      if (result.rows.length === 0) {
        throw new Error('User profile not found');
      }

      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error updating user profile', { error, auth0Sub, updates });
      throw error;
    }
  }

  async getOrCreate(
    auth0Sub: string,
    defaults: { email: string; displayName?: string }
  ): Promise<UserProfile> {
    // Try to find existing profile
    const existing = await this.getByAuth0Sub(auth0Sub);
    if (existing) {
      return existing;
    }

    // Create new profile if not found
    return await this.create(auth0Sub, defaults.email, defaults.displayName);
  }

  private mapRowToUserProfile(row: any): UserProfile {
    return {
      id: row.id,
      auth0Sub: row.auth0_sub,
      email: row.email,
      displayName: row.display_name,
      notificationEmail: row.notification_email,
      subscriptionTier: row.subscription_tier || 'free',
      emailVerified: row.email_verified ?? false,
      onboardingCompletedAt: row.onboarding_completed_at ? new Date(row.onboarding_completed_at) : null,
      deactivatedAt: row.deactivated_at ? new Date(row.deactivated_at) : null,
      deactivatedBy: row.deactivated_by || null,
      deletionRequestedAt: row.deletion_requested_at ? new Date(row.deletion_requested_at) : null,
      deletionScheduledFor: row.deletion_scheduled_for ? new Date(row.deletion_scheduled_for) : null,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at),
    };
  }

  private mapRowToUserWithAdminStatus(row: any): UserWithAdminStatus {
    return {
      ...this.mapRowToUserProfile(row),
      isAdmin: !!row.admin_auth0_sub,
      adminRole: row.admin_role || null,
      vehicleCount: parseInt(row.vehicle_count, 10) || 0,
    };
  }

  /**
   * List all users with pagination, search, and filters
   * Includes admin status by joining with admin_users table
   */
  async listAllUsers(query: ListUsersQuery): Promise<ListUsersResponse> {
    const page = query.page || 1;
    const pageSize = query.pageSize || 20;
    const offset = (page - 1) * pageSize;
    const sortBy = query.sortBy || 'createdAt';
    const sortOrder = query.sortOrder || 'desc';

    // Map sortBy to column names
    const sortColumnMap: Record<string, string> = {
      email: 'up.email',
      createdAt: 'up.created_at',
      displayName: 'up.display_name',
      subscriptionTier: 'up.subscription_tier',
      vehicleCount: 'vehicle_count',
    };
    const sortColumn = sortColumnMap[sortBy] || 'up.created_at';

    const whereClauses: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    // Search filter (email or display_name)
    if (query.search) {
      whereClauses.push(`(up.email ILIKE $${paramIndex} OR up.display_name ILIKE $${paramIndex})`);
      values.push(`%${query.search}%`);
      paramIndex++;
    }

    // Tier filter
    if (query.tier) {
      whereClauses.push(`up.subscription_tier = $${paramIndex}`);
      values.push(query.tier);
      paramIndex++;
    }

    // Status filter
    if (query.status === 'active') {
      whereClauses.push('up.deactivated_at IS NULL');
    } else if (query.status === 'deactivated') {
      whereClauses.push('up.deactivated_at IS NOT NULL');
    }
    // 'all' means no filter

    const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : '';

    // Count query
    const countQuery = `
      SELECT COUNT(*) as total
      FROM user_profiles up
      ${whereClause}
    `;

    // Data query with admin status join and vehicle count
    const dataQuery = `
      SELECT
        up.id, up.auth0_sub, up.email, up.display_name, up.notification_email,
        up.subscription_tier, up.email_verified, up.onboarding_completed_at,
        up.deactivated_at, up.deactivated_by, up.created_at, up.updated_at,
        au.auth0_sub as admin_auth0_sub,
        au.role as admin_role,
        (SELECT COUNT(*) FROM vehicles v
         WHERE v.user_id = up.auth0_sub
         AND v.is_active = true
         AND v.deleted_at IS NULL) as vehicle_count
      FROM user_profiles up
      LEFT JOIN admin_users au ON up.auth0_sub = au.auth0_sub AND au.revoked_at IS NULL
      ${whereClause}
      ORDER BY ${sortColumn} ${sortOrder === 'desc' ? 'DESC' : 'ASC'} NULLS LAST
      LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
    `;

    try {
      const [countResult, dataResult] = await Promise.all([
        this.pool.query(countQuery, values),
        this.pool.query(dataQuery, [...values, pageSize, offset]),
      ]);

      const total = parseInt(countResult.rows[0]?.total || '0', 10);
      const users = dataResult.rows.map((row) => this.mapRowToUserWithAdminStatus(row));

      return { users, total, page, pageSize };
    } catch (error) {
      logger.error('Error listing users', { error, query });
      throw error;
    }
  }

  /**
   * Get single user with admin status
   */
  async getUserWithAdminStatus(auth0Sub: string): Promise<UserWithAdminStatus | null> {
    const query = `
      SELECT
        up.id, up.auth0_sub, up.email, up.display_name, up.notification_email,
        up.subscription_tier, up.email_verified, up.onboarding_completed_at,
        up.deactivated_at, up.deactivated_by, up.created_at, up.updated_at,
        au.auth0_sub as admin_auth0_sub,
        au.role as admin_role,
        (SELECT COUNT(*) FROM vehicles v
         WHERE v.user_id = up.auth0_sub
         AND v.is_active = true
         AND v.deleted_at IS NULL) as vehicle_count
      FROM user_profiles up
      LEFT JOIN admin_users au ON up.auth0_sub = au.auth0_sub AND au.revoked_at IS NULL
      WHERE up.auth0_sub = $1
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      if (result.rows.length === 0) {
        return null;
      }
      return this.mapRowToUserWithAdminStatus(result.rows[0]);
    } catch (error) {
      logger.error('Error fetching user with admin status', { error, auth0Sub });
      throw error;
    }
  }

  /**
   * Update user subscription tier
   */
  async updateSubscriptionTier(
    auth0Sub: string,
    tier: SubscriptionTier
  ): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET subscription_tier = $1
      WHERE auth0_sub = $2
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [tier, auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error updating subscription tier', { error, auth0Sub, tier });
      throw error;
    }
  }

  /**
   * Deactivate user (soft delete)
   */
  async deactivateUser(auth0Sub: string, deactivatedBy: string): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET deactivated_at = NOW(), deactivated_by = $1
      WHERE auth0_sub = $2 AND deactivated_at IS NULL
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [deactivatedBy, auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found or already deactivated');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error deactivating user', { error, auth0Sub, deactivatedBy });
      throw error;
    }
  }

  /**
   * Reactivate user
   */
  async reactivateUser(auth0Sub: string): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET deactivated_at = NULL, deactivated_by = NULL
      WHERE auth0_sub = $1 AND deactivated_at IS NOT NULL
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found or not deactivated');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error reactivating user', { error, auth0Sub });
      throw error;
    }
  }

  /**
   * Admin update of user profile (can update email and displayName)
   */
  async adminUpdateProfile(
    auth0Sub: string,
    updates: { email?: string; displayName?: string }
  ): Promise<UserProfile> {
    const setClauses: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (updates.email !== undefined) {
      setClauses.push(`email = $${paramIndex++}`);
      values.push(updates.email);
    }

    if (updates.displayName !== undefined) {
      setClauses.push(`display_name = $${paramIndex++}`);
      values.push(updates.displayName);
    }

    if (setClauses.length === 0) {
      throw new Error('No fields to update');
    }

    values.push(auth0Sub);

    const query = `
      UPDATE user_profiles
      SET ${setClauses.join(', ')}, updated_at = NOW()
      WHERE auth0_sub = $${paramIndex}
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, values);

      if (result.rows.length === 0) {
        throw new Error('User profile not found');
      }

      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error admin updating user profile', { error, auth0Sub, updates });
      throw error;
    }
  }

  /**
   * Update email verification status
   */
  async updateEmailVerified(auth0Sub: string, emailVerified: boolean): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET email_verified = $1, updated_at = NOW()
      WHERE auth0_sub = $2
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [emailVerified, auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error updating email verified status', { error, auth0Sub, emailVerified });
      throw error;
    }
  }

  /**
   * Mark onboarding as complete
   */
  async markOnboardingComplete(auth0Sub: string): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET onboarding_completed_at = NOW(), updated_at = NOW()
      WHERE auth0_sub = $1 AND onboarding_completed_at IS NULL
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      if (result.rows.length === 0) {
        // Check if already completed or profile not found
        const existing = await this.getByAuth0Sub(auth0Sub);
        if (existing && existing.onboardingCompletedAt) {
          return existing; // Already completed, return as-is
        }
        throw new Error('User profile not found');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error marking onboarding complete', { error, auth0Sub });
      throw error;
    }
  }

  /**
   * Update user email (used when fetching correct email from Auth0)
   */
  async updateEmail(auth0Sub: string, email: string): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET email = $1, updated_at = NOW()
      WHERE auth0_sub = $2
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [email, auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error updating user email', { error, auth0Sub });
      throw error;
    }
  }

  /**
   * Request account deletion (sets deletion timestamps and deactivates account)
   * 30-day grace period before permanent deletion
   */
  async requestDeletion(auth0Sub: string): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET
        deletion_requested_at = NOW(),
        deletion_scheduled_for = NOW() + INTERVAL '30 days',
        deactivated_at = NOW(),
        updated_at = NOW()
      WHERE auth0_sub = $1 AND deletion_requested_at IS NULL
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found or deletion already requested');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error requesting account deletion', { error, auth0Sub });
      throw error;
    }
  }

  /**
   * Cancel deletion request (clears deletion timestamps and reactivates account)
   */
  async cancelDeletion(auth0Sub: string): Promise<UserProfile> {
    const query = `
      UPDATE user_profiles
      SET
        deletion_requested_at = NULL,
        deletion_scheduled_for = NULL,
        deactivated_at = NULL,
        deactivated_by = NULL,
        updated_at = NOW()
      WHERE auth0_sub = $1 AND deletion_requested_at IS NOT NULL
      RETURNING ${USER_PROFILE_COLUMNS}
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      if (result.rows.length === 0) {
        throw new Error('User profile not found or no deletion request pending');
      }
      return this.mapRowToUserProfile(result.rows[0]);
    } catch (error) {
      logger.error('Error canceling account deletion', { error, auth0Sub });
      throw error;
    }
  }

  /**
   * Get users whose deletion grace period has expired
   */
  async getUsersPastGracePeriod(): Promise<UserProfile[]> {
    const query = `
      SELECT ${USER_PROFILE_COLUMNS}
      FROM user_profiles
      WHERE deletion_scheduled_for IS NOT NULL
        AND deletion_scheduled_for <= NOW()
      ORDER BY deletion_scheduled_for ASC
    `;

    try {
      const result = await this.pool.query(query);
      return result.rows.map(row => this.mapRowToUserProfile(row));
    } catch (error) {
      logger.error('Error fetching users past grace period', { error });
      throw error;
    }
  }

  /**
   * Hard delete user and all associated data
   * This is a permanent operation - use with caution
   */
  async hardDeleteUser(auth0Sub: string): Promise<void> {
    const client = await this.pool.connect();

    try {
      await client.query('BEGIN');

      // 1. Anonymize community station submissions (keep data but remove user reference)
      await client.query(
        `UPDATE community_stations
         SET submitted_by = 'deleted-user'
         WHERE submitted_by = $1`,
        [auth0Sub]
      );

      // 2. Delete notification logs
      await client.query(
        'DELETE FROM notification_logs WHERE user_id = $1',
        [auth0Sub]
      );

      // 3. Delete user notifications
      await client.query(
        'DELETE FROM user_notifications WHERE user_id = $1',
        [auth0Sub]
      );

      // 4. Delete saved stations
      await client.query(
        'DELETE FROM saved_stations WHERE user_id = $1',
        [auth0Sub]
      );

      // 5. Delete vehicles (cascades to fuel_logs, maintenance_logs, maintenance_schedules, documents)
      await client.query(
        'DELETE FROM vehicles WHERE user_id = $1',
        [auth0Sub]
      );

      // 6. Delete user preferences
      await client.query(
        'DELETE FROM user_preferences WHERE user_id = $1',
        [auth0Sub]
      );

      // 7. Delete user profile (final step)
      await client.query(
        'DELETE FROM user_profiles WHERE auth0_sub = $1',
        [auth0Sub]
      );

      await client.query('COMMIT');

      logger.info('User hard deleted successfully', { auth0Sub });
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Error hard deleting user', { error, auth0Sub });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Get total count of active vehicles across all users
   * Used by admin stats endpoint for dashboard widget
   */
  async getTotalVehicleCount(): Promise<number> {
    const query = `
      SELECT COUNT(*) as total
      FROM vehicles
      WHERE is_active = true
        AND deleted_at IS NULL
    `;

    try {
      const result = await this.pool.query(query);
      return parseInt(result.rows[0]?.total || '0', 10);
    } catch (error) {
      logger.error('Error getting total vehicle count', { error });
      throw error;
    }
  }

  /**
   * Get total count of active users
   * Used by admin stats endpoint for dashboard widget
   */
  async getTotalUserCount(): Promise<number> {
    const query = `
      SELECT COUNT(*) as total
      FROM user_profiles
      WHERE deactivated_at IS NULL
    `;

    try {
      const result = await this.pool.query(query);
      return parseInt(result.rows[0]?.total || '0', 10);
    } catch (error) {
      logger.error('Error getting total user count', { error });
      throw error;
    }
  }

  /**
   * Get vehicles for a user (admin view)
   * Returns only year, make, model for privacy
   */
  async getUserVehiclesForAdmin(auth0Sub: string): Promise<Array<{ year: number; make: string; model: string }>> {
    const query = `
      SELECT year, make, model
      FROM vehicles
      WHERE user_id = $1
        AND is_active = true
        AND deleted_at IS NULL
      ORDER BY year DESC, make ASC, model ASC
    `;

    try {
      const result = await this.pool.query(query, [auth0Sub]);
      return result.rows.map(row => ({
        year: row.year,
        make: row.make,
        model: row.model,
      }));
    } catch (error) {
      logger.error('Error getting user vehicles for admin', { error, auth0Sub });
      throw error;
    }
  }
}
