import { Pool, QueryResult, QueryResultRow } from 'pg';
import { User, Monitor, Snapshot, Alert } from '../types';

// Convert snake_case database keys to camelCase TypeScript properties
function toCamelCase<T>(obj: any): T {
  if (obj === null || obj === undefined) return obj;
  if (Array.isArray(obj)) return obj.map(item => toCamelCase<any>(item)) as T;
  if (typeof obj !== 'object') return obj;

  const result: any = {};
  for (const key in obj) {
    const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
    let value = obj[key];

    // Parse JSON fields that are stored as strings in the database
    if ((key === 'ignore_rules' || key === 'keyword_rules') && typeof value === 'string') {
      try {
        value = JSON.parse(value);
      } catch (e) {
        // Keep as-is if parsing fails
      }
    }

    result[camelKey] = value;
  }
  return result as T;
}

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

pool.on('error', (err) => {
  console.error('Unexpected database error:', err);
  process.exit(-1);
});

export const query = async <T extends QueryResultRow = any>(
  text: string,
  params?: any[]
): Promise<QueryResult<T>> => {
  const start = Date.now();
  const result = await pool.query<T>(text, params);
  const duration = Date.now() - start;

  if (duration > 1000) {
    console.warn(`Slow query (${duration}ms):`, text);
  }

  return result;
};

export const getClient = () => pool.connect();

// User queries
export const db = {
  users: {
    async create(email: string, passwordHash: string): Promise<User> {
      const result = await query(
        'INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING *',
        [email, passwordHash]
      );
      return toCamelCase<User>(result.rows[0]);
    },

    async findById(id: string): Promise<User | null> {
      const result = await query(
        'SELECT * FROM users WHERE id = $1',
        [id]
      );
      return result.rows[0] ? toCamelCase<User>(result.rows[0]) : null;
    },

    async findByEmail(email: string): Promise<User | null> {
      const result = await query(
        'SELECT * FROM users WHERE email = $1',
        [email]
      );
      return result.rows[0] ? toCamelCase<User>(result.rows[0]) : null;
    },

    async update(id: string, updates: Partial<User>): Promise<User | null> {
      const fields = Object.keys(updates);
      const values = Object.values(updates);
      const setClause = fields.map((field, i) => `${field} = $${i + 2}`).join(', ');

      const result = await query<User>(
        `UPDATE users SET ${setClause} WHERE id = $1 RETURNING *`,
        [id, ...values]
      );
      return result.rows[0] || null;
    },

    async updateLastLogin(id: string): Promise<void> {
      await query('UPDATE users SET last_login_at = NOW() WHERE id = $1', [id]);
    },

    async updatePassword(id: string, passwordHash: string): Promise<void> {
      await query('UPDATE users SET password_hash = $1 WHERE id = $2', [passwordHash, id]);
    },

    async updateNotificationSettings(
      id: string,
      settings: {
        emailEnabled?: boolean;
        webhookUrl?: string | null;
        webhookEnabled?: boolean;
        slackWebhookUrl?: string | null;
        slackEnabled?: boolean;
      }
    ): Promise<void> {
      const updates: string[] = [];
      const values: any[] = [];
      let paramIndex = 1;

      if (settings.emailEnabled !== undefined) {
        updates.push(`email_enabled = $${paramIndex++}`);
        values.push(settings.emailEnabled);
      }
      if (settings.webhookUrl !== undefined) {
        updates.push(`webhook_url = $${paramIndex++}`);
        values.push(settings.webhookUrl);
      }
      if (settings.webhookEnabled !== undefined) {
        updates.push(`webhook_enabled = $${paramIndex++}`);
        values.push(settings.webhookEnabled);
      }
      if (settings.slackWebhookUrl !== undefined) {
        updates.push(`slack_webhook_url = $${paramIndex++}`);
        values.push(settings.slackWebhookUrl);
      }
      if (settings.slackEnabled !== undefined) {
        updates.push(`slack_enabled = $${paramIndex++}`);
        values.push(settings.slackEnabled);
      }

      if (updates.length > 0) {
        values.push(id);
        await query(
          `UPDATE users SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
          values
        );
      }
    },

    async delete(id: string): Promise<boolean> {
      const result = await query('DELETE FROM users WHERE id = $1', [id]);
      return (result.rowCount ?? 0) > 0;
    },

    async verifyEmail(email: string): Promise<void> {
      await query(
        'UPDATE users SET email_verified = true, email_verified_at = NOW() WHERE email = $1',
        [email]
      );
    },
  },

  monitors: {
    async create(data: Omit<Monitor, 'id' | 'createdAt' | 'updatedAt' | 'consecutiveErrors'>): Promise<Monitor> {
      const result = await query(
        `INSERT INTO monitors (
          user_id, url, name, frequency, status, element_selector,
          ignore_rules, keyword_rules
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
        [
          data.userId,
          data.url,
          data.name,
          data.frequency,
          data.status,
          data.elementSelector || null,
          data.ignoreRules ? JSON.stringify(data.ignoreRules) : null,
          data.keywordRules ? JSON.stringify(data.keywordRules) : null,
        ]
      );
      return toCamelCase<Monitor>(result.rows[0]);
    },

    async findById(id: string): Promise<Monitor | null> {
      const result = await query(
        'SELECT * FROM monitors WHERE id = $1',
        [id]
      );
      return result.rows[0] ? toCamelCase<Monitor>(result.rows[0]) : null;
    },

    async findByUserId(userId: string): Promise<Monitor[]> {
      const result = await query(
        'SELECT * FROM monitors WHERE user_id = $1 ORDER BY created_at DESC',
        [userId]
      );
      return result.rows.map(row => toCamelCase<Monitor>(row));
    },

    async countByUserId(userId: string): Promise<number> {
      const result = await query<{ count: string }>(
        'SELECT COUNT(*) as count FROM monitors WHERE user_id = $1',
        [userId]
      );
      return parseInt(result.rows[0].count);
    },

    async findActiveMonitors(): Promise<Monitor[]> {
      const result = await query(
        'SELECT * FROM monitors WHERE status = $1',
        ['active']
      );
      return result.rows.map(row => toCamelCase<Monitor>(row));
    },

    async update(id: string, updates: Partial<Monitor>): Promise<Monitor | null> {
      const fields: string[] = [];
      const values: any[] = [];
      let paramCount = 2;

      Object.entries(updates).forEach(([key, value]) => {
        if (value !== undefined) {
          const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
          if (key === 'ignoreRules' || key === 'keywordRules') {
            fields.push(`${snakeKey} = $${paramCount}`);
            values.push(JSON.stringify(value));
          } else {
            fields.push(`${snakeKey} = $${paramCount}`);
            values.push(value);
          }
          paramCount++;
        }
      });

      if (fields.length === 0) return null;

      const result = await query(
        `UPDATE monitors SET ${fields.join(', ')} WHERE id = $1 RETURNING *`,
        [id, ...values]
      );
      return result.rows[0] ? toCamelCase<Monitor>(result.rows[0]) : null;
    },

    async delete(id: string): Promise<boolean> {
      const result = await query('DELETE FROM monitors WHERE id = $1', [id]);
      return (result.rowCount ?? 0) > 0;
    },

    async updateLastChecked(id: string, changed: boolean): Promise<void> {
      if (changed) {
        await query(
          "UPDATE monitors SET last_checked_at = NOW(), last_changed_at = NOW(), consecutive_errors = 0, status = 'active' WHERE id = $1",
          [id]
        );
      } else {
        await query(
          "UPDATE monitors SET last_checked_at = NOW(), consecutive_errors = 0, status = 'active' WHERE id = $1",
          [id]
        );
      }
    },

    async incrementErrors(id: string): Promise<void> {
      await query(
        "UPDATE monitors SET last_checked_at = NOW(), consecutive_errors = consecutive_errors + 1, status = CASE WHEN consecutive_errors >= 0 THEN 'error' ELSE status END WHERE id = $1",
        [id]
      );
    },
  },

  snapshots: {
    async create(data: Omit<Snapshot, 'id' | 'createdAt'>): Promise<Snapshot> {
      const result = await query(
        `INSERT INTO snapshots (
          monitor_id, html_content, text_content, content_hash, screenshot_url,
          http_status, response_time, changed, change_percentage, error_message,
          importance_score, summary
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
        [
          data.monitorId,
          data.htmlContent,
          data.textContent,
          data.contentHash,
          data.screenshotUrl || null,
          data.httpStatus,
          data.responseTime,
          data.changed,
          data.changePercentage || null,
          data.errorMessage || null,
          data.importanceScore ?? 0,
          data.summary || null,
        ]
      );
      return toCamelCase<Snapshot>(result.rows[0]);
    },

    async findByMonitorId(monitorId: string, limit = 50): Promise<Snapshot[]> {
      const result = await query(
        'SELECT * FROM snapshots WHERE monitor_id = $1 ORDER BY created_at DESC LIMIT $2',
        [monitorId, limit]
      );
      return result.rows.map(row => toCamelCase<Snapshot>(row));
    },

    async findLatestByMonitorId(monitorId: string): Promise<Snapshot | null> {
      const result = await query(
        'SELECT * FROM snapshots WHERE monitor_id = $1 ORDER BY created_at DESC LIMIT 1',
        [monitorId]
      );
      return result.rows[0] ? toCamelCase<Snapshot>(result.rows[0]) : null;
    },

    async findById(id: string): Promise<Snapshot | null> {
      const result = await query(
        'SELECT * FROM snapshots WHERE id = $1',
        [id]
      );
      return result.rows[0] ? toCamelCase<Snapshot>(result.rows[0]) : null;
    },

    async deleteOldSnapshots(monitorId: string, keepCount: number): Promise<void> {
      await query(
        `DELETE FROM snapshots
         WHERE monitor_id = $1
         AND id NOT IN (
           SELECT id FROM snapshots
           WHERE monitor_id = $1
           ORDER BY created_at DESC
           LIMIT $2
         )`,
        [monitorId, keepCount]
      );
    },

    async deleteOldSnapshotsByAge(monitorId: string, retentionDays: number): Promise<void> {
      await query(
        `DELETE FROM snapshots
         WHERE monitor_id = $1
         AND created_at < NOW() - INTERVAL '1 day' * $2`,
        [monitorId, retentionDays]
      );
    },
  },

  alerts: {
    async create(data: Omit<Alert, 'id' | 'createdAt' | 'deliveredAt' | 'readAt'>): Promise<Alert> {
      const result = await query(
        `INSERT INTO alerts (
          monitor_id, snapshot_id, user_id, type, title, summary, channels
        ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
        [
          data.monitorId,
          data.snapshotId,
          data.userId,
          data.type,
          data.title,
          data.summary || null,
          JSON.stringify(data.channels),
        ]
      );
      return toCamelCase<Alert>(result.rows[0]);
    },

    async findByUserId(userId: string, limit = 50): Promise<Alert[]> {
      const result = await query(
        'SELECT * FROM alerts WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2',
        [userId, limit]
      );
      return result.rows.map(row => toCamelCase<Alert>(row));
    },

    async markAsDelivered(id: string): Promise<void> {
      await query('UPDATE alerts SET delivered_at = NOW() WHERE id = $1', [id]);
    },

    async markAsRead(id: string): Promise<void> {
      await query('UPDATE alerts SET read_at = NOW() WHERE id = $1', [id]);
    },

    async updateChannels(id: string, channels: string[]): Promise<void> {
      await query('UPDATE alerts SET channels = $1 WHERE id = $2', [JSON.stringify(channels), id]);
    },
  },

  webhookLogs: {
    async create(data: {
      userId: string;
      monitorId?: string;
      alertId?: string;
      webhookType: 'webhook' | 'slack';
      url: string;
      payload?: any;
      statusCode?: number;
      responseBody?: string;
      success: boolean;
      errorMessage?: string;
      attempt?: number;
    }): Promise<{ id: string }> {
      const result = await query(
        `INSERT INTO webhook_logs (
          user_id, monitor_id, alert_id, webhook_type, url, payload,
          status_code, response_body, success, error_message, attempt
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
        [
          data.userId,
          data.monitorId || null,
          data.alertId || null,
          data.webhookType,
          data.url,
          data.payload ? JSON.stringify(data.payload) : null,
          data.statusCode || null,
          data.responseBody || null,
          data.success,
          data.errorMessage || null,
          data.attempt || 1,
        ]
      );
      return { id: result.rows[0].id };
    },

    async findByUserId(userId: string, limit = 100): Promise<any[]> {
      const result = await query(
        'SELECT * FROM webhook_logs WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2',
        [userId, limit]
      );
      return result.rows.map(row => toCamelCase<any>(row));
    },

    async findFailedByUserId(userId: string, limit = 50): Promise<any[]> {
      const result = await query(
        'SELECT * FROM webhook_logs WHERE user_id = $1 AND success = false ORDER BY created_at DESC LIMIT $2',
        [userId, limit]
      );
      return result.rows.map(row => toCamelCase<any>(row));
    },
  },
};

export default db;
