import pool from '../config/database';
import logger from '../utils/logger';
import {
  MitgliederProfile,
  MemberWithProfile,
  MemberListItem,
  MemberFilters,
  MemberStats,
  CreateMemberProfileData,
  UpdateMemberProfileData,
  DienstgradVerlaufEntry,
} from '../models/member.model';

class MemberService {
  // ----------------------------------------------------------------
  // Private helpers
  // ----------------------------------------------------------------

  /**
   * Builds the SELECT columns and JOIN for a full MemberWithProfile query.
   * Returns raw pg rows that map to MemberWithProfile.
   */
  private buildMemberSelectQuery(): string {
    return `
      SELECT
        u.id,
        u.email,
        u.name,
        u.given_name,
        u.family_name,
        u.preferred_username,
        u.profile_picture_url,
        u.is_active,
        u.last_login_at,
        u.created_at,
        -- profile columns (aliased with mp_ prefix to avoid collision)
        mp.id                     AS mp_id,
        mp.user_id                AS mp_user_id,
        mp.fdisk_standesbuch_nr   AS mp_fdisk_standesbuch_nr,
        mp.dienstgrad             AS mp_dienstgrad,
        mp.dienstgrad_seit        AS mp_dienstgrad_seit,
        mp.funktion               AS mp_funktion,
        mp.status                 AS mp_status,
        mp.eintrittsdatum         AS mp_eintrittsdatum,
        mp.austrittsdatum         AS mp_austrittsdatum,
        mp.geburtsdatum           AS mp_geburtsdatum,
        mp.telefon_mobil          AS mp_telefon_mobil,
        mp.telefon_privat         AS mp_telefon_privat,
        mp.notfallkontakt_name    AS mp_notfallkontakt_name,
        mp.notfallkontakt_telefon AS mp_notfallkontakt_telefon,
        mp.fuehrerscheinklassen   AS mp_fuehrerscheinklassen,
        mp.tshirt_groesse         AS mp_tshirt_groesse,
        mp.schuhgroesse           AS mp_schuhgroesse,
        mp.bemerkungen            AS mp_bemerkungen,
        mp.bild_url               AS mp_bild_url,
        mp.geburtsort             AS mp_geburtsort,
        mp.geschlecht             AS mp_geschlecht,
        mp.beruf                  AS mp_beruf,
        mp.wohnort                AS mp_wohnort,
        mp.plz                    AS mp_plz,
        mp.created_at             AS mp_created_at,
        mp.updated_at             AS mp_updated_at
      FROM users u
      LEFT JOIN mitglieder_profile mp ON mp.user_id = u.id
    `;
  }

  /**
   * Maps a raw pg result row (with mp_ prefixed columns) into a
   * MemberWithProfile object. Handles null profile gracefully.
   */
  private mapRowToMemberWithProfile(row: any): MemberWithProfile {
    const hasProfile = row.mp_id !== null;

    return {
      id: row.id,
      email: row.email,
      name: row.name,
      given_name: row.given_name,
      family_name: row.family_name,
      preferred_username: row.preferred_username,
      profile_picture_url: row.profile_picture_url,
      is_active: row.is_active,
      last_login_at: row.last_login_at,
      created_at: row.created_at,
      profile: hasProfile
        ? {
            id: row.mp_id,
            user_id: row.mp_user_id,
            fdisk_standesbuch_nr: row.mp_fdisk_standesbuch_nr ?? null,
            dienstgrad: row.mp_dienstgrad,
            dienstgrad_seit: row.mp_dienstgrad_seit,
            funktion: row.mp_funktion ?? [],
            status: row.mp_status,
            eintrittsdatum: row.mp_eintrittsdatum,
            austrittsdatum: row.mp_austrittsdatum,
            geburtsdatum: row.mp_geburtsdatum,
            telefon_mobil: row.mp_telefon_mobil,
            telefon_privat: row.mp_telefon_privat,
            notfallkontakt_name: row.mp_notfallkontakt_name,
            notfallkontakt_telefon: row.mp_notfallkontakt_telefon,
            fuehrerscheinklassen: row.mp_fuehrerscheinklassen ?? [],
            tshirt_groesse: row.mp_tshirt_groesse,
            schuhgroesse: row.mp_schuhgroesse,
            bemerkungen: row.mp_bemerkungen,
            bild_url: row.mp_bild_url,
            geburtsort: row.mp_geburtsort ?? null,
            geschlecht: row.mp_geschlecht ?? null,
            beruf: row.mp_beruf ?? null,
            wohnort: row.mp_wohnort ?? null,
            plz: row.mp_plz ?? null,
            created_at: row.mp_created_at,
            updated_at: row.mp_updated_at,
          }
        : null,
    };
  }

  // ----------------------------------------------------------------
  // Public API
  // ----------------------------------------------------------------

  /**
   * Returns a paginated list of members with the minimal fields
   * required by the list view. Supports free-text search and
   * multi-value filter by status and dienstgrad.
   */
  async getAllMembers(filters?: MemberFilters): Promise<{ items: MemberListItem[]; total: number }> {
    try {
      const {
        search,
        status,
        dienstgrad,
        page = 1,
        pageSize = 25,
      } = filters ?? {};

      const conditions: string[] = ['u.is_active = TRUE'];
      const values: any[] = [];
      let paramIdx = 1;

      if (search) {
        conditions.push(`(
          u.name ILIKE $${paramIdx}
          OR u.email ILIKE $${paramIdx}
          OR u.given_name ILIKE $${paramIdx}
          OR u.family_name ILIKE $${paramIdx}
        )`);
        values.push(`%${search}%`);
        paramIdx++;
      }

      if (status && status.length > 0) {
        conditions.push(`mp.status = ANY($${paramIdx}::VARCHAR[])`);
        values.push(status);
        paramIdx++;
      }

      if (dienstgrad && dienstgrad.length > 0) {
        conditions.push(`mp.dienstgrad = ANY($${paramIdx}::VARCHAR[])`);
        values.push(dienstgrad);
        paramIdx++;
      }

      const whereClause = `WHERE ${conditions.join(' AND ')}`;
      const fetchAll = pageSize === 0;

      let dataQuery: string;
      if (fetchAll) {
        dataQuery = `
          SELECT
            u.id,
            u.name,
            u.given_name,
            u.family_name,
            u.email,
            u.profile_picture_url,
            u.is_active,
            mp.id            AS profile_id,
            mp.fdisk_standesbuch_nr,
            mp.dienstgrad,
            mp.funktion,
            mp.status,
            mp.eintrittsdatum,
            mp.telefon_mobil
          FROM users u
          LEFT JOIN mitglieder_profile mp ON mp.user_id = u.id
          ${whereClause}
          ORDER BY u.family_name ASC NULLS LAST, u.given_name ASC NULLS LAST
        `;
      } else {
        const offset = (page - 1) * pageSize;
        dataQuery = `
          SELECT
            u.id,
            u.name,
            u.given_name,
            u.family_name,
            u.email,
            u.profile_picture_url,
            u.is_active,
            mp.id            AS profile_id,
            mp.fdisk_standesbuch_nr,
            mp.dienstgrad,
            mp.funktion,
            mp.status,
            mp.eintrittsdatum,
            mp.telefon_mobil
          FROM users u
          LEFT JOIN mitglieder_profile mp ON mp.user_id = u.id
          ${whereClause}
          ORDER BY u.family_name ASC NULLS LAST, u.given_name ASC NULLS LAST
          LIMIT $${paramIdx} OFFSET $${paramIdx + 1}
        `;
        values.push(pageSize, offset);
      }

      const countQuery = `
        SELECT COUNT(*)::INTEGER AS total
        FROM users u
        LEFT JOIN mitglieder_profile mp ON mp.user_id = u.id
        ${whereClause}
      `;

      const countValues = fetchAll ? values : values.slice(0, values.length - 2);

      const [dataResult, countResult] = await Promise.all([
        pool.query(dataQuery, values),
        pool.query(countQuery, countValues),
      ]);

      const items: MemberListItem[] = dataResult.rows.map((row) => ({
        id: row.id,
        name: row.name,
        given_name: row.given_name,
        family_name: row.family_name,
        email: row.email,
        profile_picture_url: row.profile_picture_url,
        is_active: row.is_active,
        profile_id: row.profile_id ?? null,
        fdisk_standesbuch_nr: row.fdisk_standesbuch_nr ?? null,
        dienstgrad: row.dienstgrad ?? null,
        funktion: row.funktion ?? [],
        status: row.status ?? null,
        eintrittsdatum: row.eintrittsdatum ?? null,
        telefon_mobil: row.telefon_mobil ?? null,
      }));

      logger.debug('getAllMembers', { count: items.length, filters });
      return { items, total: countResult.rows[0].total };
    } catch (error) {
      logger.error('Error fetching member list', { error, filters });
      throw new Error('Failed to fetch members');
    }
  }

  /**
   * Returns a single member with their full profile (including rank history).
   * Returns null when the user does not exist.
   */
  async getMemberById(userId: string): Promise<MemberWithProfile | null> {
    try {
      const query = `${this.buildMemberSelectQuery()} WHERE u.id = $1`;
      const result = await pool.query(query, [userId]);

      if (result.rows.length === 0) {
        logger.debug('getMemberById: not found', { userId });
        return null;
      }

      const member = this.mapRowToMemberWithProfile(result.rows[0]);

      // Attach rank history when the profile exists
      if (member.profile) {
        member.dienstgrad_verlauf = await this.getDienstgradVerlauf(userId);
      } else {
        member.dienstgrad_verlauf = [];
      }

      return member;
    } catch (error) {
      logger.error('Error fetching member by id', { error, userId });
      throw new Error('Failed to fetch member');
    }
  }

  /**
   * Looks up a member by their Authentik OIDC subject identifier.
   * Useful for looking up the currently logged-in user's own profile.
   */
  async getMemberByAuthentikSub(sub: string): Promise<MemberWithProfile | null> {
    try {
      const query = `${this.buildMemberSelectQuery()} WHERE u.authentik_sub = $1`;
      const result = await pool.query(query, [sub]);

      if (result.rows.length === 0) return null;

      const member = this.mapRowToMemberWithProfile(result.rows[0]);
      if (member.profile) {
        member.dienstgrad_verlauf = await this.getDienstgradVerlauf(member.id);
      }

      return member;
    } catch (error) {
      logger.error('Error fetching member by authentik sub', { error, sub });
      throw new Error('Failed to fetch member');
    }
  }

  /**
   * Creates the mitglieder_profile row for an existing auth user.
   * Throws if a profile already exists for this user_id.
   */
  async createMemberProfile(
    userId: string,
    data: CreateMemberProfileData
  ): Promise<MitgliederProfile> {
    try {
      const query = `
        INSERT INTO mitglieder_profile (
          user_id,
          fdisk_standesbuch_nr,
          dienstgrad,
          dienstgrad_seit,
          funktion,
          status,
          eintrittsdatum,
          austrittsdatum,
          geburtsdatum,
          telefon_mobil,
          telefon_privat,
          notfallkontakt_name,
          notfallkontakt_telefon,
          fuehrerscheinklassen,
          tshirt_groesse,
          schuhgroesse,
          bemerkungen,
          bild_url
        ) VALUES (
          $1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
          $11, $12, $13, $14, $15, $16, $17, $18
        )
        RETURNING *
      `;

      const values = [
        userId,
        data.fdisk_standesbuch_nr ?? null,
        data.dienstgrad ?? null,
        data.dienstgrad_seit ?? null,
        data.funktion ?? [],
        data.status ?? 'aktiv',
        data.eintrittsdatum ?? null,
        data.austrittsdatum ?? null,
        data.geburtsdatum ?? null,
        data.telefon_mobil ?? null,
        data.telefon_privat ?? null,
        data.notfallkontakt_name ?? null,
        data.notfallkontakt_telefon ?? null,
        data.fuehrerscheinklassen ?? [],
        data.tshirt_groesse ?? null,
        data.schuhgroesse ?? null,
        data.bemerkungen ?? null,
        data.bild_url ?? null,
      ];

      const result = await pool.query(query, values);
      const profile = result.rows[0] as MitgliederProfile;

      // If a dienstgrad was set at creation, record it in history
      if (data.dienstgrad) {
        await this.writeDienstgradVerlauf(
          userId,
          data.dienstgrad,
          null,
          userId, // created-by = the user being set up (or override with system user)
          'Initialer Dienstgrad bei Profilerstellung'
        );
      }

      logger.info('Created mitglieder_profile', { userId, profileId: profile.id });
      return profile;
    } catch (error: any) {
      if (error?.code === '23505') {
        // unique_violation on user_id
        throw new Error('Ein Profil für dieses Mitglied existiert bereits.');
      }
      logger.error('Error creating mitglieder_profile', { error, userId });
      throw new Error('Failed to create member profile');
    }
  }

  /**
   * Partially updates the mitglieder_profile for the given user.
   * Only fields present in `data` are written (undefined = untouched).
   *
   * Rank changes are handled separately via updateDienstgrad() to ensure
   * the change is always logged in dienstgrad_verlauf. If `data.dienstgrad`
   * is present it will be delegated to that method automatically.
   */
  async updateMemberProfile(
    userId: string,
    data: UpdateMemberProfileData,
    updatedBy: string
  ): Promise<MitgliederProfile> {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');

      // Handle rank change through dedicated method to ensure audit log
      const { dienstgrad, dienstgrad_seit, ...rest } = data;

      if (dienstgrad !== undefined) {
        await this.updateDienstgrad(userId, dienstgrad, updatedBy, dienstgrad_seit, client);
      } else if (dienstgrad_seit !== undefined) {
        // dienstgrad_seit can be updated independently
        (rest as any).dienstgrad_seit = dienstgrad_seit;
      }

      // Build dynamic SET clause for remaining fields
      const updateFields: string[] = [];
      const values: any[] = [];
      let paramIdx = 1;

      const fieldMap: Record<string, any> = {
        fdisk_standesbuch_nr: rest.fdisk_standesbuch_nr,
        funktion: rest.funktion,
        status: rest.status,
        eintrittsdatum: rest.eintrittsdatum,
        austrittsdatum: rest.austrittsdatum,
        geburtsdatum: rest.geburtsdatum,
        telefon_mobil: rest.telefon_mobil,
        telefon_privat: rest.telefon_privat,
        notfallkontakt_name: rest.notfallkontakt_name,
        notfallkontakt_telefon: rest.notfallkontakt_telefon,
        fuehrerscheinklassen: rest.fuehrerscheinklassen,
        tshirt_groesse: rest.tshirt_groesse,
        schuhgroesse: rest.schuhgroesse,
        bemerkungen: rest.bemerkungen,
        bild_url: rest.bild_url,
        dienstgrad_seit: (rest as any).dienstgrad_seit,
      };

      for (const [column, value] of Object.entries(fieldMap)) {
        if (value !== undefined) {
          updateFields.push(`${column} = $${paramIdx++}`);
          values.push(value);
        }
      }

      let profile: MitgliederProfile;

      if (updateFields.length > 0) {
        values.push(userId);
        const query = `
          UPDATE mitglieder_profile
          SET ${updateFields.join(', ')}
          WHERE user_id = $${paramIdx}
          RETURNING *
        `;
        const result = await client.query(query, values);

        if (result.rows.length === 0) {
          throw new Error('Mitgliedsprofil nicht gefunden.');
        }
        profile = result.rows[0] as MitgliederProfile;
      } else {
        // Nothing to update (rank change only) — fetch current state
        const result = await client.query(
          'SELECT * FROM mitglieder_profile WHERE user_id = $1',
          [userId]
        );
        if (result.rows.length === 0) throw new Error('Mitgliedsprofil nicht gefunden.');
        profile = result.rows[0] as MitgliederProfile;
      }

      await client.query('COMMIT');
      logger.info('Updated mitglieder_profile', { userId, updatedBy });
      return profile;
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Error updating mitglieder_profile', { error, userId });
      throw error instanceof Error ? error : new Error('Failed to update member profile');
    } finally {
      client.release();
    }
  }

  /**
   * Sets a new Dienstgrad and writes an entry to dienstgrad_verlauf.
   * Can accept an optional pg PoolClient to participate in an outer transaction.
   */
  async updateDienstgrad(
    userId: string,
    newDienstgrad: string,
    changedBy: string,
    since?: Date,
    existingClient?: any
  ): Promise<void> {
    const executor = existingClient ?? pool;

    try {
      // Fetch current rank for the history entry
      const currentResult = await executor.query(
        'SELECT dienstgrad FROM mitglieder_profile WHERE user_id = $1',
        [userId]
      );

      const oldDienstgrad: string | null =
        currentResult.rows.length > 0 ? currentResult.rows[0].dienstgrad : null;

      // Update the profile
      await executor.query(
        `UPDATE mitglieder_profile
         SET dienstgrad = $1, dienstgrad_seit = $2
         WHERE user_id = $3`,
        [newDienstgrad, since ?? new Date(), userId]
      );

      // Write audit entry
      await this.writeDienstgradVerlauf(
        userId,
        newDienstgrad,
        oldDienstgrad,
        changedBy,
        null,
        existingClient
      );

      logger.info('Updated Dienstgrad', { userId, oldDienstgrad, newDienstgrad, changedBy });
    } catch (error) {
      logger.error('Error updating Dienstgrad', { error, userId, newDienstgrad });
      throw new Error('Failed to update Dienstgrad');
    }
  }

  /**
   * Internal helper: inserts one row into dienstgrad_verlauf.
   */
  private async writeDienstgradVerlauf(
    userId: string,
    dienstgradNeu: string,
    dienstgradAlt: string | null,
    durchUserId: string | null,
    bemerkung: string | null = null,
    existingClient?: any
  ): Promise<void> {
    const executor = existingClient ?? pool;
    await executor.query(
      `INSERT INTO dienstgrad_verlauf
         (user_id, dienstgrad_neu, dienstgrad_alt, durch_user_id, bemerkung)
       VALUES ($1, $2, $3, $4, $5)`,
      [userId, dienstgradNeu, dienstgradAlt, durchUserId, bemerkung]
    );
  }

  /**
   * Fetches the rank change history for a member, newest first.
   */
  async getDienstgradVerlauf(userId: string): Promise<DienstgradVerlaufEntry[]> {
    try {
      const result = await pool.query(
        `SELECT
           dv.*,
           u.name AS durch_user_name
         FROM dienstgrad_verlauf dv
         LEFT JOIN users u ON u.id = dv.durch_user_id
         WHERE dv.user_id = $1
         ORDER BY dv.datum DESC, dv.created_at DESC`,
        [userId]
      );
      return result.rows as DienstgradVerlaufEntry[];
    } catch (error) {
      logger.error('Error fetching Dienstgrad history', { error, userId });
      return [];
    }
  }

  /**
   * Ensures a mitglieder_profile row exists for the given user.
   * Safe to call on every login — idempotent via ON CONFLICT DO NOTHING.
   * Errors are caught and logged without throwing.
   */
  async ensureProfileExists(userId: string): Promise<void> {
    try {
      await pool.query(
        `INSERT INTO mitglieder_profile (user_id, status)
         VALUES ($1, 'aktiv')
         ON CONFLICT (user_id) DO NOTHING`,
        [userId]
      );
    } catch (error) {
      logger.warn('ensureProfileExists failed (non-fatal)', { error, userId });
    }
  }

  /**
   * Returns aggregate member counts, used by the dashboard KPI cards.
   * Optionally scoped to a single status value.
   */
  async getMemberCount(status?: string): Promise<number> {
    try {
      let query: string;
      let values: any[];

      if (status) {
        query = `
          SELECT COUNT(*)::INTEGER AS count
          FROM mitglieder_profile
          WHERE status = $1
        `;
        values = [status];
      } else {
        query = `SELECT COUNT(*)::INTEGER AS count FROM mitglieder_profile`;
        values = [];
      }

      const result = await pool.query(query, values);
      return result.rows[0].count;
    } catch (error) {
      logger.error('Error counting members', { error, status });
      throw new Error('Failed to count members');
    }
  }

  /**
   * Returns a full stats breakdown for all statuses (dashboard KPI widget).
   */
  async getMemberStats(): Promise<MemberStats> {
    try {
      const result = await pool.query(`
        SELECT
          COUNT(*)::INTEGER                                        AS total,
          COUNT(*) FILTER (WHERE status = 'aktiv')::INTEGER       AS aktiv,
          COUNT(*) FILTER (WHERE status = 'kind')::INTEGER        AS kind,
          COUNT(*) FILTER (WHERE status = 'jugend')::INTEGER      AS jugend,
          COUNT(*) FILTER (WHERE status = 'reserve')::INTEGER     AS reserve
        FROM mitglieder_profile
      `);

      return (result.rows[0] as MemberStats) ?? {
        total: 0,
        aktiv: 0,
        kind: 0,
        jugend: 0,
        reserve: 0,
      };
    } catch (error) {
      logger.error('Error fetching member stats', { error });
      throw new Error('Failed to fetch member stats');
    }
  }

  /**
   * Returns all Beförderungen for a member, newest first.
   */
  async getBefoerderungen(userId: string): Promise<any[]> {
    try {
      const result = await pool.query(
        `SELECT id, datum, dienstgrad, created_at
         FROM befoerderungen
         WHERE user_id = $1
         ORDER BY datum DESC NULLS LAST, created_at DESC`,
        [userId]
      );
      return result.rows;
    } catch (error) {
      logger.error('Error fetching Beförderungen', { error, userId });
      return [];
    }
  }

  /**
   * Returns all Untersuchungen for a member, newest first.
   */
  async getUntersuchungen(userId: string): Promise<any[]> {
    try {
      const result = await pool.query(
        `SELECT id, datum, anmerkungen, art, ergebnis, created_at
         FROM untersuchungen
         WHERE user_id = $1
         ORDER BY datum DESC NULLS LAST, created_at DESC`,
        [userId]
      );
      return result.rows;
    } catch (error) {
      logger.error('Error fetching Untersuchungen', { error, userId });
      return [];
    }
  }

  /**
   * Returns all Fahrgenehmigungen for a member, newest first.
   */
  async getFahrgenehmigungen(userId: string): Promise<any[]> {
    try {
      const result = await pool.query(
        `SELECT id, ausstellungsdatum, gueltig_bis, behoerde, nummer, klasse, created_at
         FROM fahrgenehmigungen
         WHERE user_id = $1
         ORDER BY ausstellungsdatum DESC NULLS LAST, created_at DESC`,
        [userId]
      );
      return result.rows;
    } catch (error) {
      logger.error('Error fetching Fahrgenehmigungen', { error, userId });
      return [];
    }
  }

  /**
   * Returns all Ausbildungen (training courses) for a given user from the FDISK-synced table.
   */
  async getAusbildungen(userId: string): Promise<any[]> {
    try {
      const result = await pool.query(
        `SELECT id, kursname, kursnummer, kurs_kurzbezeichnung, erfolgscode, kurs_datum, ablaufdatum, ort, bemerkung, status, created_at
         FROM ausbildung
         WHERE user_id = $1
         ORDER BY kurs_datum DESC NULLS LAST, created_at DESC`,
        [userId]
      );
      return result.rows;
    } catch (error) {
      logger.error('Error fetching Ausbildungen', { error, userId });
      return [];
    }
  }
}

export default new MemberService();
