import { Pool } from 'pg';
import pool from '../../../core/config/database';
import type {
  EmailTemplate,
  NotificationLog,
  NotificationSummary,
  DueMaintenanceItem,
  ExpiringDocument,
  TemplateKey,
  UserNotification,
  UnreadNotificationCount,
  SentNotificationRecord
} from '../domain/notifications.types';

export class NotificationsRepository {
  constructor(private readonly db: Pool = pool) {}

  // ========================
  // Row Mappers
  // ========================

  private mapEmailTemplate(row: any): EmailTemplate {
    return {
      id: row.id,
      templateKey: row.template_key,
      name: row.name,
      description: row.description,
      subject: row.subject,
      body: row.body,
      variables: row.variables,
      isActive: row.is_active,
      createdAt: row.created_at,
      updatedAt: row.updated_at
    };
  }

  private mapDueMaintenanceItem(row: any): DueMaintenanceItem {
    return {
      id: row.id,
      vehicleId: row.vehicle_id,
      vehicleName: row.vehicle_name,
      category: row.category,
      subtypes: row.subtypes,
      nextDueDate: row.next_due_date,
      nextDueMileage: row.next_due_mileage,
      isDueSoon: row.is_due_soon,
      isOverdue: row.is_overdue,
      emailNotifications: row.email_notifications,
      reminderDays1: row.reminder_1_days,
      reminderDays2: row.reminder_2_days,
      reminderDays3: row.reminder_3_days
    };
  }

  private mapExpiringDocument(row: any): ExpiringDocument {
    return {
      id: row.id,
      vehicleId: row.vehicle_id,
      vehicleName: row.vehicle_name,
      documentType: row.document_type,
      title: row.title,
      expirationDate: row.expiration_date,
      isExpiringSoon: row.is_expiring_soon,
      isExpired: row.is_expired,
      emailNotifications: row.email_notifications
    };
  }

  private mapUserNotification(row: any): UserNotification {
    return {
      id: row.id,
      userId: row.user_id,
      notificationType: row.notification_type,
      title: row.title,
      message: row.message,
      referenceType: row.reference_type,
      referenceId: row.reference_id,
      vehicleId: row.vehicle_id,
      isRead: row.is_read,
      createdAt: row.created_at,
      readAt: row.read_at
    };
  }

  private mapSentNotificationRecord(row: any): SentNotificationRecord {
    return {
      id: row.id,
      scheduleId: row.schedule_id,
      notificationDate: row.notification_date,
      reminderLevel: row.reminder_level,
      deliveryMethod: row.delivery_method,
      createdAt: row.created_at
    };
  }

  // ========================
  // Email Templates
  // ========================

  async getEmailTemplates(): Promise<EmailTemplate[]> {
    const res = await this.db.query(
      `SELECT * FROM email_templates ORDER BY template_key`
    );
    return res.rows.map(row => this.mapEmailTemplate(row));
  }

  async getEmailTemplateByKey(key: TemplateKey): Promise<EmailTemplate | null> {
    const res = await this.db.query(
      `SELECT * FROM email_templates WHERE template_key = $1`,
      [key]
    );
    return res.rows[0] ? this.mapEmailTemplate(res.rows[0]) : null;
  }

  async updateEmailTemplate(
    key: TemplateKey,
    updates: { subject?: string; body?: string; isActive?: boolean }
  ): Promise<EmailTemplate | null> {
    const fields: string[] = [];
    const params: (string | boolean)[] = [];
    let i = 1;

    if (updates.subject !== undefined) {
      fields.push(`subject = $${i++}`);
      params.push(updates.subject);
    }
    if (updates.body !== undefined) {
      fields.push(`body = $${i++}`);
      params.push(updates.body);
    }
    if (updates.isActive !== undefined) {
      fields.push(`is_active = $${i++}`);
      params.push(updates.isActive);
    }

    if (fields.length === 0) {
      return this.getEmailTemplateByKey(key);
    }

    fields.push(`updated_at = CURRENT_TIMESTAMP`);
    params.push(key);

    const sql = `
      UPDATE email_templates
      SET ${fields.join(', ')}
      WHERE template_key = $${i}
      RETURNING *
    `;

    const res = await this.db.query(sql, params);
    return res.rows[0] ? this.mapEmailTemplate(res.rows[0]) : null;
  }

  // ========================
  // Notification Logs
  // ========================

  async insertNotificationLog(log: {
    user_id: string;
    notification_type: 'email' | 'toast';
    template_key: TemplateKey;
    recipient_email?: string;
    subject?: string;
    reference_type?: string;
    reference_id?: string;
    status?: 'pending' | 'sent' | 'failed';
    error_message?: string;
  }): Promise<NotificationLog> {
    const res = await this.db.query(
      `INSERT INTO notification_logs (
        user_id, notification_type, template_key, recipient_email, subject,
        reference_type, reference_id, status, error_message
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
      RETURNING *`,
      [
        log.user_id,
        log.notification_type,
        log.template_key,
        log.recipient_email ?? null,
        log.subject ?? null,
        log.reference_type ?? null,
        log.reference_id ?? null,
        log.status ?? 'sent',
        log.error_message ?? null,
      ]
    );
    return res.rows[0] as NotificationLog;
  }

  // ========================
  // Notification Summary
  // ========================

  async getNotificationSummary(userId: string): Promise<NotificationSummary> {
    // Get counts of due soon vs overdue maintenance items
    const maintenanceRes = await this.db.query(
      `SELECT
         COUNT(*) FILTER (WHERE ms.next_due_date <= CURRENT_DATE) as overdue_count,
         COUNT(*) FILTER (WHERE ms.next_due_date > CURRENT_DATE AND ms.next_due_date <= CURRENT_DATE + INTERVAL '30 days') as due_soon_count
       FROM maintenance_schedules ms
       WHERE ms.user_id = $1
         AND ms.is_active = true
         AND ms.next_due_date IS NOT NULL
         AND ms.next_due_date <= CURRENT_DATE + INTERVAL '30 days'`,
      [userId]
    );

    // Get counts of expiring soon vs expired documents
    const documentRes = await this.db.query(
      `SELECT
         COUNT(*) FILTER (WHERE d.expiration_date < CURRENT_DATE) as expired_count,
         COUNT(*) FILTER (WHERE d.expiration_date >= CURRENT_DATE AND d.expiration_date <= CURRENT_DATE + INTERVAL '30 days') as expiring_soon_count
       FROM documents d
       WHERE d.user_id = $1
         AND d.deleted_at IS NULL
         AND d.expiration_date IS NOT NULL
         AND d.expiration_date <= CURRENT_DATE + INTERVAL '30 days'`,
      [userId]
    );

    return {
      maintenanceDueSoon: parseInt(maintenanceRes.rows[0]?.due_soon_count || '0', 10),
      maintenanceOverdue: parseInt(maintenanceRes.rows[0]?.overdue_count || '0', 10),
      documentsExpiringSoon: parseInt(documentRes.rows[0]?.expiring_soon_count || '0', 10),
      documentsExpired: parseInt(documentRes.rows[0]?.expired_count || '0', 10),
    };
  }

  // ========================
  // Due Maintenance Items
  // ========================

  async getDueMaintenanceItems(userId: string): Promise<DueMaintenanceItem[]> {
    const res = await this.db.query(
      `SELECT
         ms.id,
         ms.vehicle_id,
         v.name as vehicle_name,
         ms.category,
         ms.subtypes,
         ms.next_due_date,
         ms.next_due_mileage,
         ms.email_notifications,
         ms.reminder_1_days,
         ms.reminder_2_days,
         ms.reminder_3_days,
         CASE
           WHEN ms.next_due_date <= CURRENT_DATE THEN true
           WHEN ms.next_due_mileage IS NOT NULL AND v.odometer >= ms.next_due_mileage THEN true
           ELSE false
         END as is_overdue,
         CASE
           WHEN ms.next_due_date <= CURRENT_DATE + INTERVAL '30 days' AND ms.next_due_date > CURRENT_DATE THEN true
           WHEN ms.next_due_mileage IS NOT NULL AND v.odometer >= ms.next_due_mileage - 500 AND v.odometer < ms.next_due_mileage THEN true
           ELSE false
         END as is_due_soon
       FROM maintenance_schedules ms
       JOIN vehicles v ON ms.vehicle_id = v.id
       WHERE ms.user_id = $1
         AND ms.is_active = true
         AND (
           ms.next_due_date <= CURRENT_DATE + INTERVAL '30 days'
           OR (ms.next_due_mileage IS NOT NULL AND v.odometer >= ms.next_due_mileage - 500)
         )
       ORDER BY
         CASE WHEN ms.next_due_date <= CURRENT_DATE THEN 0 ELSE 1 END,
         ms.next_due_date ASC NULLS LAST`,
      [userId]
    );
    return res.rows.map(row => this.mapDueMaintenanceItem(row));
  }

  // ========================
  // Expiring Documents
  // ========================

  async getExpiringDocuments(userId: string): Promise<ExpiringDocument[]> {
    const res = await this.db.query(
      `SELECT
         d.id,
         d.vehicle_id,
         v.name as vehicle_name,
         d.document_type,
         d.title,
         d.expiration_date,
         d.email_notifications,
         CASE
           WHEN d.expiration_date < CURRENT_DATE THEN true
           ELSE false
         END as is_expired,
         CASE
           WHEN d.expiration_date >= CURRENT_DATE AND d.expiration_date <= CURRENT_DATE + INTERVAL '30 days' THEN true
           ELSE false
         END as is_expiring_soon
       FROM documents d
       JOIN vehicles v ON d.vehicle_id = v.id
       WHERE d.user_id = $1
         AND d.deleted_at IS NULL
         AND d.expiration_date IS NOT NULL
         AND d.expiration_date <= CURRENT_DATE + INTERVAL '30 days'
       ORDER BY
         CASE WHEN d.expiration_date < CURRENT_DATE THEN 0 ELSE 1 END,
         d.expiration_date ASC`,
      [userId]
    );
    return res.rows.map(row => this.mapExpiringDocument(row));
  }

  // ========================
  // User Notifications
  // ========================

  async insertUserNotification(notification: {
    userId: string;
    notificationType: string;
    title: string;
    message: string;
    referenceType?: string;
    referenceId?: string;
    vehicleId?: string;
  }): Promise<UserNotification> {
    const res = await this.db.query(
      `INSERT INTO user_notifications (
        id, user_id, notification_type, title, message,
        reference_type, reference_id, vehicle_id
      ) VALUES (uuid_generate_v4(), $1, $2, $3, $4, $5, $6, $7)
      RETURNING *`,
      [
        notification.userId,
        notification.notificationType,
        notification.title,
        notification.message,
        notification.referenceType ?? null,
        notification.referenceId ?? null,
        notification.vehicleId ?? null
      ]
    );
    return this.mapUserNotification(res.rows[0]);
  }

  async findUserNotifications(
    userId: string,
    limit: number = 20,
    includeRead: boolean = false
  ): Promise<UserNotification[]> {
    const sql = `
      SELECT *
      FROM user_notifications
      WHERE user_id = $1
        ${includeRead ? '' : 'AND is_read = false'}
      ORDER BY created_at DESC
      LIMIT $2
    `;

    const res = await this.db.query(sql, [userId, limit]);
    return res.rows.map(row => this.mapUserNotification(row));
  }

  async getUnreadCount(userId: string): Promise<UnreadNotificationCount> {
    const res = await this.db.query(
      `SELECT
         COUNT(*) as total,
         COUNT(*) FILTER (WHERE notification_type = 'maintenance') as maintenance,
         COUNT(*) FILTER (WHERE notification_type = 'document') as documents
       FROM user_notifications
       WHERE user_id = $1
         AND is_read = false`,
      [userId]
    );

    const row = res.rows[0];
    return {
      total: parseInt(row.total || '0', 10),
      maintenance: parseInt(row.maintenance || '0', 10),
      documents: parseInt(row.documents || '0', 10)
    };
  }

  async markAsRead(id: string, userId: string): Promise<UserNotification | null> {
    const res = await this.db.query(
      `UPDATE user_notifications
       SET is_read = true, read_at = CURRENT_TIMESTAMP
       WHERE id = $1 AND user_id = $2
       RETURNING *`,
      [id, userId]
    );
    return res.rows[0] ? this.mapUserNotification(res.rows[0]) : null;
  }

  async markAllAsRead(userId: string): Promise<number> {
    const res = await this.db.query(
      `UPDATE user_notifications
       SET is_read = true, read_at = CURRENT_TIMESTAMP
       WHERE user_id = $1 AND is_read = false
       RETURNING id`,
      [userId]
    );
    return res.rowCount || 0;
  }

  async deleteUserNotification(id: string, userId: string): Promise<void> {
    await this.db.query(
      `DELETE FROM user_notifications
       WHERE id = $1 AND user_id = $2`,
      [id, userId]
    );
  }

  // ========================
  // Sent Notification Tracker
  // ========================

  async hasNotificationBeenSent(
    scheduleId: string,
    notificationDate: string,
    reminderLevel: 1 | 2 | 3
  ): Promise<boolean> {
    const res = await this.db.query(
      `SELECT EXISTS (
        SELECT 1
        FROM sent_notification_tracker
        WHERE schedule_id = $1
          AND notification_date = $2
          AND reminder_level = $3
      ) as exists`,
      [scheduleId, notificationDate, reminderLevel]
    );
    return res.rows[0]?.exists || false;
  }

  async recordSentNotification(record: {
    scheduleId: string;
    notificationDate: string;
    reminderLevel: 1 | 2 | 3;
    deliveryMethod: 'email' | 'in_app' | 'both';
  }): Promise<SentNotificationRecord> {
    const res = await this.db.query(
      `INSERT INTO sent_notification_tracker (
        schedule_id, notification_date, reminder_level, delivery_method
      ) VALUES ($1, $2, $3, $4)
      RETURNING *`,
      [
        record.scheduleId,
        record.notificationDate,
        record.reminderLevel,
        record.deliveryMethod
      ]
    );
    return this.mapSentNotificationRecord(res.rows[0]);
  }

  async getUsersWithActiveSchedules(): Promise<Array<{
    userId: string;
    userEmail: string;
    userName: string;
  }>> {
    const res = await this.db.query(
      `SELECT DISTINCT
         ms.user_id,
         up.email as user_email,
         up.name as user_name
       FROM maintenance_schedules ms
       JOIN user_profiles up ON ms.user_id = up.user_id
       WHERE ms.is_active = true
         AND (
           ms.reminder_1_days IS NOT NULL
           OR ms.reminder_2_days IS NOT NULL
           OR ms.reminder_3_days IS NOT NULL
         )
       ORDER BY ms.user_id`
    );

    return res.rows.map(row => ({
      userId: row.user_id,
      userEmail: row.user_email,
      userName: row.user_name
    }));
  }
}
