<?php
/**
 * Meter repository.
 *
 * @package KGV\VereinManager
 */

namespace KGV\VereinManager\Repositories;

use KGV\VereinManager\Schema;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MeterRepository extends AbstractRepository {

	/**
	 * Resolve table name.
	 *
	 * @return string
	 */
	protected function resolve_table() {
		return Schema::table( 'meters' );
	}

	/**
	 * Search meters.
	 *
	 * @param array $args Query arguments.
	 * @return array
	 */
	public function search( $args = array() ) {
		$search     = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
		$type       = isset( $args['type'] ) ? sanitize_key( wp_unslash( $args['type'] ) ) : '';
		$section_id = isset( $args['section_id'] ) ? absint( $args['section_id'] ) : 0;
		$assignment = isset( $args['assignment'] ) ? sanitize_key( wp_unslash( $args['assignment'] ) ) : '';
		$orderby    = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'meter_number', array( 'meter_number', 'type', 'installed_at', 'calibration_year', 'is_active', 'created_at' ), 'meter_number' );
		$order      = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );

		$sections = Schema::table( 'sections' );
		$parcels  = Schema::table( 'parcels' );

		$sql = "SELECT m.*, s.name AS section_name, p.label AS parcel_label
			FROM {$this->table} m
			LEFT JOIN {$sections} s ON s.id = m.section_id
			LEFT JOIN {$parcels} p ON p.id = m.parcel_id
			WHERE 1=1";

		$params = array();

		if ( '' !== $search ) {
			$like     = '%' . $this->wpdb->esc_like( $search ) . '%';
			$sql     .= ' AND (m.meter_number LIKE %s OR m.note LIKE %s)';
			$params[] = $like;
			$params[] = $like;
		}

		if ( in_array( $type, array( 'water', 'power' ), true ) ) {
			$sql     .= ' AND m.type = %s';
			$params[] = $type;
		}

		if ( $section_id > 0 ) {
			$sql     .= ' AND m.section_id = %d';
			$params[] = $section_id;
		}

		if ( 'free' === $assignment ) {
			$sql .= ' AND m.parcel_id IS NULL AND m.is_main_meter = 0';
		} elseif ( 'assigned' === $assignment ) {
			$sql .= ' AND m.parcel_id IS NOT NULL';
		} elseif ( 'main' === $assignment ) {
			$sql .= ' AND m.is_main_meter = 1';
		}

		$sql .= " ORDER BY m.{$orderby} {$order}, m.id DESC";

		if ( ! empty( $params ) ) {
			return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		}

		return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
	}

	/**
	 * Save or update meter.
	 *
	 * @param array $data Meter data.
	 * @param int   $id   Optional ID.
	 * @return int|false
	 */
	public function save( $data, $id = 0 ) {
		$current       = $id > 0 ? $this->find( $id ) : null;
		$is_main_meter = isset( $data['is_main_meter'] ) ? absint( $data['is_main_meter'] ) : ( $current ? (int) $current->is_main_meter : 0 );

		if ( empty( $data['is_active'] ) ) {
			$is_main_meter = 0;
		}

		$payload = array(
			'type'             => $data['type'],
			'meter_number'     => $data['meter_number'],
			'section_id'       => $data['section_id'],
			'parcel_id'        => $current ? $current->parcel_id : null,
			'installed_at'     => $data['installed_at'] ? $data['installed_at'] : null,
			'calibration_year' => isset( $data['calibration_year'] ) ? $data['calibration_year'] : null,
			'is_main_meter'    => $is_main_meter,
			'is_active'        => $data['is_active'],
			'note'             => $data['note'],
			'updated_at'       => $this->now(),
		);

		$formats = array( '%s', '%s', '%d', '%s', '%s', '%d', '%d', '%d', '%s', '%s' );

		if ( $id > 0 ) {
			$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
			return $id;
		}

		$payload['created_at'] = $this->now();
		$this->wpdb->insert( $this->table, $payload, array( '%s', '%s', '%d', '%s', '%s', '%d', '%d', '%d', '%s', '%s', '%s' ) );

		return $this->wpdb->insert_id;
	}

	/**
	 * Check if a meter number already exists for the same type.
	 *
	 * @param string $meter_number Meter number.
	 * @param string $type         Meter type.
	 * @param int    $exclude_id   Optional ID to exclude.
	 * @return bool
	 */
	public function meter_number_exists( $meter_number, $type, $exclude_id = 0 ) {
		$sql    = "SELECT COUNT(*) FROM {$this->table} WHERE meter_number = %s AND type = %s";
		$params = array( $meter_number, $type );

		if ( $exclude_id > 0 ) {
			$sql      .= ' AND id != %d';
			$params[] = $exclude_id;
		}

		return (int) $this->wpdb->get_var( $this->wpdb->prepare( $sql, $params ) ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Return free meters for selection.
	 *
	 * @param string $type              water|power.
	 * @param int    $section_id        Optional section filter.
	 * @param int    $current_parcel_id Current parcel in edit mode.
	 * @return array
	 */
	public function get_free_by_type( $type, $section_id = 0, $current_parcel_id = 0 ) {
		$sql    = "SELECT * FROM {$this->table} WHERE type = %s AND is_active = 1 AND is_main_meter = 0 AND (parcel_id IS NULL OR parcel_id = %d)";
		$params = array( $type, $current_parcel_id );

		if ( $section_id > 0 ) {
			$sql     .= ' AND section_id = %d';
			$params[] = $section_id;
		}

		$sql .= ' ORDER BY meter_number ASC';

		return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Get the meter assigned to a parcel by type.
	 *
	 * @param int    $parcel_id Parcel ID.
	 * @param string $type      Meter type.
	 * @return object|null
	 */
	public function get_assigned_to_parcel( $parcel_id, $type ) {
		return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->table} WHERE parcel_id = %d AND type = %s LIMIT 1", $parcel_id, $type ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Load a meter if it can legally be assigned to a parcel.
	 *
	 * @param int    $id                Meter ID.
	 * @param string $type              Expected type.
	 * @param int    $section_id        Target section ID.
	 * @param int    $current_parcel_id Current parcel ID.
	 * @return object|null
	 */
	public function get_assignable_meter( $id, $type, $section_id, $current_parcel_id ) {
		$sql = "SELECT * FROM {$this->table} WHERE id = %d AND type = %s AND section_id = %d AND is_active = 1 AND is_main_meter = 0 AND (parcel_id IS NULL OR parcel_id = %d) LIMIT 1";
		return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $id, $type, $section_id, $current_parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Release all meters assigned to a parcel.
	 *
	 * @param int $parcel_id Parcel ID.
	 * @return void
	 */
	public function release_parcel( $parcel_id ) {
		$this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->table} SET parcel_id = NULL, updated_at = %s WHERE parcel_id = %d", $this->now(), $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Assign a meter to a parcel.
	 *
	 * @param int $meter_id  Meter ID.
	 * @param int $parcel_id Parcel ID.
	 * @return void
	 */
	public function assign_to_parcel( $meter_id, $parcel_id ) {
		$this->wpdb->update(
			$this->table,
			array(
				'parcel_id'      => $parcel_id,
				'is_main_meter'  => 0,
				'updated_at'     => $this->now(),
			),
			array( 'id' => $meter_id ),
			array( '%d', '%d', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Release one specific meter from its parcel.
	 *
	 * @param int $meter_id Meter ID.
	 * @return void
	 */
	public function release_meter( $meter_id ) {
		$this->wpdb->update(
			$this->table,
			array(
				'parcel_id'  => null,
				'updated_at' => $this->now(),
			),
			array( 'id' => $meter_id ),
			array( '%d', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * Return all available main meters for one section.
	 *
	 * @param int $section_id Section ID.
	 * @return array
	 */
	public function get_available_main_for_section( $section_id ) {
		$sql = "SELECT * FROM {$this->table} WHERE section_id = %d AND is_active = 1 AND parcel_id IS NULL ORDER BY type ASC, meter_number ASC";
		return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Return all selected main meters for one section.
	 *
	 * @param int $section_id Section ID.
	 * @return array
	 */
	public function get_main_for_section( $section_id ) {
		$sql = "SELECT * FROM {$this->table} WHERE section_id = %d AND is_main_meter = 1 ORDER BY type ASC, meter_number ASC";
		return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Sync selected main meters for a section.
	 *
	 * @param int   $section_id Section ID.
	 * @param array $meter_ids  Selected meter IDs.
	 * @return void
	 */
	public function sync_main_meters_for_section( $section_id, $meter_ids ) {
		$section_id = absint( $section_id );
		$meter_ids  = array_values( array_unique( array_filter( array_map( 'absint', (array) $meter_ids ) ) ) );

		if ( $section_id < 1 ) {
			return;
		}

		$this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->table} SET is_main_meter = 0, updated_at = %s WHERE section_id = %d AND parcel_id IS NULL", $this->now(), $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		if ( empty( $meter_ids ) ) {
			return;
		}

		$placeholders = implode( ', ', array_fill( 0, count( $meter_ids ), '%d' ) );
		$params       = array_merge( array( $this->now(), $section_id ), $meter_ids );
		$sql          = "UPDATE {$this->table} SET is_main_meter = 1, parcel_id = NULL, updated_at = %s WHERE section_id = %d AND is_active = 1 AND parcel_id IS NULL AND id IN ({$placeholders})";

		$this->wpdb->query( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Check whether the meter is already assigned.
	 *
	 * @param int $id Meter ID.
	 * @return bool
	 */
	public function is_assigned( $id ) {
		$meter = $this->find( $id );
		return $meter && ! empty( $meter->parcel_id );
	}
}
