/**
 * @ai-summary Data access layer for community-submitted gas stations
 * @ai-context Parameterized SQL queries for CRUD operations and filtering
 */

import { Pool } from 'pg';
import {
  CommunityStation,
  CommunityStationFilters,
  CommunityStationListResult,
  SubmitCommunityStationBody,
  StationBounds,
  RemovalReportResult
} from '../domain/community-stations.types';
import { logger } from '../../../core/logging/logger';

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

  async submitStation(
    userId: string,
    data: SubmitCommunityStationBody
  ): Promise<CommunityStation> {
    const query = `
      INSERT INTO community_stations (
        submitted_by,
        name,
        address,
        city,
        state,
        zip_code,
        latitude,
        longitude,
        brand,
        has_93_octane,
        has_93_octane_ethanol_free,
        price_93,
        notes,
        status
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 'approved')
      RETURNING *
    `;

    try {
      const result = await this.pool.query(query, [
        userId,
        data.name,
        data.address,
        data.city || null,
        data.state || null,
        data.zipCode || null,
        data.latitude,
        data.longitude,
        data.brand || null,
        data.has93Octane ?? true,
        data.has93OctaneEthanolFree ?? false,
        data.price93 || null,
        data.notes || null
      ]);

      if (result.rows.length === 0) {
        throw new Error('Failed to submit station');
      }

      return this.mapRow(result.rows[0]);
    } catch (error) {
      logger.error('Error submitting station', { error, userId });
      throw error;
    }
  }

  async getStationById(stationId: string): Promise<CommunityStation | null> {
    const query = `
      SELECT * FROM community_stations
      WHERE id = $1
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [stationId]);
      if (result.rows.length === 0) {
        return null;
      }
      return this.mapRow(result.rows[0]);
    } catch (error) {
      logger.error('Error fetching station by id', { error, stationId });
      throw error;
    }
  }

  async getUserSubmissions(userId: string, limit: number = 100, offset: number = 0): Promise<CommunityStationListResult> {
    const countQuery = `
      SELECT COUNT(*) as total FROM community_stations
      WHERE submitted_by = $1
    `;

    const dataQuery = `
      SELECT * FROM community_stations
      WHERE submitted_by = $1
      ORDER BY created_at DESC
      LIMIT $2 OFFSET $3
    `;

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

      const total = parseInt(countResult.rows[0].total, 10);
      const stations = dataResult.rows.map(row => this.mapRow(row));

      return { total, stations };
    } catch (error) {
      logger.error('Error fetching user submissions', { error, userId });
      throw error;
    }
  }

  async getApprovedStations(limit: number = 100, offset: number = 0): Promise<CommunityStationListResult> {
    const countQuery = `
      SELECT COUNT(*) as total FROM community_stations
      WHERE status = 'approved'
    `;

    const dataQuery = `
      SELECT * FROM community_stations
      WHERE status = 'approved'
      ORDER BY created_at DESC
      LIMIT $1 OFFSET $2
    `;

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

      const total = parseInt(countResult.rows[0].total, 10);
      const stations = dataResult.rows.map(row => this.mapRow(row));

      return { total, stations };
    } catch (error) {
      logger.error('Error fetching approved stations', { error });
      throw error;
    }
  }

  async getNearbyApprovedStations(
    latitude: number,
    longitude: number,
    radiusKm: number = 50
  ): Promise<CommunityStation[]> {
    // Convert km to degrees (approximately 1 degree = 111 km)
    const radiusDegrees = radiusKm / 111;

    const query = `
      SELECT *,
        (6371 * acos(cos(radians($1)) * cos(radians(latitude)) *
        cos(radians(longitude) - radians($2)) +
        sin(radians($1)) * sin(radians(latitude)))) AS distance_km
      FROM community_stations
      WHERE status = 'approved'
        AND latitude BETWEEN $1 - $3 AND $1 + $3
        AND longitude BETWEEN $2 - $3 AND $2 + $3
      ORDER BY distance_km ASC
      LIMIT 50
    `;

    try {
      const result = await this.pool.query(query, [latitude, longitude, radiusDegrees]);
      return result.rows.map(row => this.mapRow(row));
    } catch (error) {
      logger.error('Error fetching nearby stations', { error, latitude, longitude });
      throw error;
    }
  }

  async getPendingStations(limit: number = 100, offset: number = 0): Promise<CommunityStationListResult> {
    const countQuery = `
      SELECT COUNT(*) as total FROM community_stations
      WHERE status = 'pending'
    `;

    const dataQuery = `
      SELECT * FROM community_stations
      WHERE status = 'pending'
      ORDER BY created_at ASC
      LIMIT $1 OFFSET $2
    `;

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

      const total = parseInt(countResult.rows[0].total, 10);
      const stations = dataResult.rows.map(row => this.mapRow(row));

      return { total, stations };
    } catch (error) {
      logger.error('Error fetching pending stations', { error });
      throw error;
    }
  }

  async getAllStationsWithFilters(filters: CommunityStationFilters): Promise<CommunityStationListResult> {
    const limit = filters.limit || 100;
    const offset = filters.offset || 0;

    const params: any[] = [];
    let whereConditions = '';

    if (filters.status) {
      whereConditions += `status = $${params.length + 1}`;
      params.push(filters.status);
    }

    if (filters.submittedBy) {
      if (whereConditions) whereConditions += ' AND ';
      whereConditions += `submitted_by = $${params.length + 1}`;
      params.push(filters.submittedBy);
    }

    const whereClause = whereConditions ? `WHERE ${whereConditions}` : '';

    const countQuery = `
      SELECT COUNT(*) as total FROM community_stations
      ${whereClause}
    `;

    const dataQuery = `
      SELECT * FROM community_stations
      ${whereClause}
      ORDER BY created_at DESC
      LIMIT $${params.length + 1} OFFSET $${params.length + 2}
    `;

    try {
      const countParams = whereConditions ? params.slice() : [];
      const dataParams = [...params, limit, offset];

      const [countResult, dataResult] = await Promise.all([
        this.pool.query(countQuery, countParams),
        this.pool.query(dataQuery, dataParams)
      ]);

      const total = parseInt(countResult.rows[0].total, 10);
      const stations = dataResult.rows.map(row => this.mapRow(row));

      return { total, stations };
    } catch (error) {
      logger.error('Error fetching stations with filters', { error, filters });
      throw error;
    }
  }

  async reviewStation(
    stationId: string,
    adminId: string,
    status: 'approved' | 'rejected',
    rejectionReason?: string
  ): Promise<CommunityStation> {
    const query = `
      UPDATE community_stations
      SET status = $1,
          reviewed_by = $2,
          reviewed_at = CURRENT_TIMESTAMP,
          rejection_reason = $3,
          updated_at = CURRENT_TIMESTAMP
      WHERE id = $4
      RETURNING *
    `;

    try {
      const result = await this.pool.query(query, [
        status,
        adminId,
        rejectionReason || null,
        stationId
      ]);

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

      return this.mapRow(result.rows[0]);
    } catch (error) {
      logger.error('Error reviewing station', { error, stationId });
      throw error;
    }
  }

  async deleteStation(stationId: string): Promise<boolean> {
    const query = 'DELETE FROM community_stations WHERE id = $1';

    try {
      const result = await this.pool.query(query, [stationId]);
      return (result.rowCount ?? 0) > 0;
    } catch (error) {
      logger.error('Error deleting station', { error, stationId });
      throw error;
    }
  }

  private mapRow(row: any): CommunityStation {
    return {
      id: row.id,
      submittedBy: row.submitted_by,
      name: row.name,
      address: row.address,
      city: row.city,
      state: row.state,
      zipCode: row.zip_code,
      latitude: parseFloat(row.latitude),
      longitude: parseFloat(row.longitude),
      brand: row.brand,
      has93Octane: row.has_93_octane ?? false,
      has93OctaneEthanolFree: row.has_93_octane_ethanol_free ?? false,
      price93: row.price_93 ? parseFloat(row.price_93) : undefined,
      notes: row.notes,
      status: row.status,
      reviewedBy: row.reviewed_by,
      reviewedAt: row.reviewed_at ? new Date(row.reviewed_at) : undefined,
      rejectionReason: row.rejection_reason,
      removalReportCount: row.removal_report_count ?? 0,
      removedAt: row.removed_at ? new Date(row.removed_at) : undefined,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at)
    };
  }

  /**
   * Submit a removal report for a station
   * Returns the new count and whether the station was removed (2+ reports)
   */
  async submitRemovalReport(
    userId: string,
    stationId: string,
    reason: string = 'No longer has Premium 93'
  ): Promise<RemovalReportResult> {
    const client = await this.pool.connect();

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

      // Insert removal report (will fail if duplicate due to unique constraint)
      await client.query(`
        INSERT INTO station_removal_reports (station_id, reported_by, reason)
        VALUES ($1, $2, $3)
      `, [stationId, userId, reason]);

      // Get updated count (trigger updates it automatically)
      const countResult = await client.query(`
        SELECT removal_report_count FROM community_stations WHERE id = $1
      `, [stationId]);

      const reportCount = countResult.rows[0]?.removal_report_count ?? 0;
      let stationRemoved = false;

      // If 2+ reports, mark as removed
      if (reportCount >= 2) {
        await client.query(`
          UPDATE community_stations
          SET status = 'removed',
              removed_at = CURRENT_TIMESTAMP,
              updated_at = CURRENT_TIMESTAMP
          WHERE id = $1
        `, [stationId]);
        stationRemoved = true;
      }

      await client.query('COMMIT');

      return { reportCount, stationRemoved };
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Error submitting removal report', { error, userId, stationId });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Check if a user has already reported a station
   */
  async hasUserReportedStation(userId: string, stationId: string): Promise<boolean> {
    const query = `
      SELECT 1 FROM station_removal_reports
      WHERE station_id = $1 AND reported_by = $2
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [stationId, userId]);
      return result.rows.length > 0;
    } catch (error) {
      logger.error('Error checking user report status', { error, userId, stationId });
      throw error;
    }
  }

  /**
   * Find a station by its location (latitude/longitude)
   * Uses a small tolerance for coordinate matching
   */
  async findStationByLocation(
    latitude: number,
    longitude: number,
    toleranceDegrees: number = 0.0001
  ): Promise<CommunityStation | null> {
    const query = `
      SELECT * FROM community_stations
      WHERE latitude BETWEEN ($1::NUMERIC - $3::NUMERIC) AND ($1::NUMERIC + $3::NUMERIC)
        AND longitude BETWEEN ($2::NUMERIC - $3::NUMERIC) AND ($2::NUMERIC + $3::NUMERIC)
        AND status = 'approved'
      ORDER BY
        (latitude - $1::NUMERIC) * (latitude - $1::NUMERIC) + (longitude - $2::NUMERIC) * (longitude - $2::NUMERIC) ASC
      LIMIT 1
    `;

    try {
      const result = await this.pool.query(query, [latitude, longitude, toleranceDegrees]);
      if (result.rows.length === 0) {
        return null;
      }
      return this.mapRow(result.rows[0]);
    } catch (error) {
      logger.error('Error finding station by location', { error, latitude, longitude });
      throw error;
    }
  }

  /**
   * Update the octane flags for an existing station
   * Used when a duplicate submission is detected
   */
  async updateStationOctaneFlags(
    stationId: string,
    has93Octane: boolean,
    has93OctaneEthanolFree: boolean
  ): Promise<CommunityStation> {
    const query = `
      UPDATE community_stations
      SET
        has_93_octane = $2,
        has_93_octane_ethanol_free = $3,
        updated_at = CURRENT_TIMESTAMP
      WHERE id = $1
      RETURNING *
    `;

    try {
      const result = await this.pool.query(query, [
        stationId,
        has93Octane,
        has93OctaneEthanolFree
      ]);

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

      return this.mapRow(result.rows[0]);
    } catch (error) {
      logger.error('Error updating station octane flags', { error, stationId });
      throw error;
    }
  }

  /**
   * Get approved stations within map bounds
   */
  async getApprovedStationsInBounds(bounds: StationBounds): Promise<CommunityStation[]> {
    const query = `
      SELECT * FROM community_stations
      WHERE status = 'approved'
        AND latitude BETWEEN $1 AND $2
        AND longitude BETWEEN $3 AND $4
      ORDER BY created_at DESC
      LIMIT 100
    `;

    try {
      const result = await this.pool.query(query, [
        bounds.south,
        bounds.north,
        bounds.west,
        bounds.east
      ]);
      return result.rows.map(row => this.mapRow(row));
    } catch (error) {
      logger.error('Error fetching stations in bounds', { error, bounds });
      throw error;
    }
  }
}
