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

namespace KGV\VereinManager\Repositories;

use KGV\VereinManager\Schema;

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

class MeterReadingRepository extends AbstractRepository {

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

	/**
	 * Store a new reading.
	 *
	 * @param array $data Reading data.
	 * @return int|false
	 */
	public function save( $data ) {
		$payload = array(
			'meter_id'        => absint( $data['meter_id'] ),
			'parcel_id'       => absint( $data['parcel_id'] ),
			'reading_value'   => (float) $data['reading_value'],
			'reading_date'    => $data['reading_date'],
			'note'            => $data['note'],
			'submitted_by'    => absint( $data['submitted_by'] ),
			'is_self_reading' => ! empty( $data['is_self_reading'] ) ? 1 : 0,
			'created_at'      => $this->now(),
		);

		$this->wpdb->insert(
			$this->table,
			$payload,
			array( '%d', '%d', '%f', '%s', '%s', '%d', '%d', '%s' )
		);

		return $this->wpdb->insert_id;
	}

	/**
	 * Get the latest reading for one meter.
	 *
	 * @param int $meter_id Meter ID.
	 * @return object|null
	 */
	public function get_latest_for_meter( $meter_id ) {
		$sql = "SELECT * FROM {$this->table} WHERE meter_id = %d ORDER BY reading_date DESC, id DESC LIMIT 1";
		return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $meter_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Get recent readings for a parcel.
	 *
	 * @param int $parcel_id Parcel ID.
	 * @param int $limit     Number of rows.
	 * @return array
	 */
	public function get_recent_for_parcel( $parcel_id, $limit = 10 ) {
		$meters = Schema::table( 'meters' );
		$sql    = "SELECT r.*, m.type, m.meter_number
			FROM {$this->table} r
			LEFT JOIN {$meters} m ON m.id = r.meter_id
			WHERE r.parcel_id = %d
			ORDER BY r.reading_date DESC, r.id DESC
			LIMIT %d";

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

	/**
	 * Get a filtered consumption report including deltas since the previous reading.
	 *
	 * @param int    $section_id Optional section filter.
	 * @param string $date_from  Optional start date.
	 * @param string $date_to    Optional end date.
	 * @param string $order      ASC|DESC by reading date.
	 * @return array
	 */
	public function get_consumption_report( $section_id = 0, $date_from = '', $date_to = '', $order = 'DESC' ) {
		$meters   = Schema::table( 'meters' );
		$sections = Schema::table( 'sections' );
		$parcels  = Schema::table( 'parcels' );
		$sql      = "SELECT r.*, m.type, m.meter_number, m.section_id, s.name AS section_name, p.label AS parcel_label
			FROM {$this->table} r
			LEFT JOIN {$meters} m ON m.id = r.meter_id
			LEFT JOIN {$sections} s ON s.id = m.section_id
			LEFT JOIN {$parcels} p ON p.id = r.parcel_id
			WHERE 1=1";
		$params   = array();
		$order    = 'ASC' === strtoupper( (string) $order ) ? 'ASC' : 'DESC';

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

		if ( '' !== $date_to ) {
			$sql     .= ' AND r.reading_date <= %s';
			$params[] = $date_to;
		}

		$sql .= ' ORDER BY r.meter_id ASC, r.reading_date ASC, r.id ASC';

		$rows = ! empty( $params )
			? $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			: $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery

		$report       = array();
		$last_by_meter = array();

		foreach ( $rows as $row ) {
			$previous             = isset( $last_by_meter[ $row->meter_id ] ) ? $last_by_meter[ $row->meter_id ] : null;
			$row->previous_value  = $previous ? (float) $previous->reading_value : null;
			$row->consumption     = $previous && (float) $row->reading_value >= (float) $previous->reading_value ? (float) $row->reading_value - (float) $previous->reading_value : null;
			$last_by_meter[$row->meter_id] = $row;

			if ( '' !== $date_from && $row->reading_date < $date_from ) {
				continue;
			}

			$report[] = $row;
		}

		usort(
			$report,
			function( $left, $right ) use ( $order ) {
				$left_key  = $left->reading_date . ' ' . str_pad( (string) $left->id, 10, '0', STR_PAD_LEFT );
				$right_key = $right->reading_date . ' ' . str_pad( (string) $right->id, 10, '0', STR_PAD_LEFT );

				if ( $left_key === $right_key ) {
					return 0;
				}

				if ( 'ASC' === $order ) {
					return $left_key < $right_key ? -1 : 1;
				}

				return $left_key > $right_key ? -1 : 1;
			}
		);

		return $report;
	}

	/**
	 * Get all readings for a parcel, suitable for export.
	 *
	 * @param int $parcel_id Parcel ID.
	 * @return array
	 */
	public function get_all_for_parcel( $parcel_id ) {
		$meters = Schema::table( 'meters' );
		$sql    = "SELECT r.*, m.type, m.meter_number
			FROM {$this->table} r
			LEFT JOIN {$meters} m ON m.id = r.meter_id
			WHERE r.parcel_id = %d
			ORDER BY r.reading_date DESC, r.id DESC";

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

	/**
	 * Get reading history for a specific meter.
	 *
	 * @param int $meter_id Meter ID.
	 * @param int $limit    Max rows.
	 * @return array
	 */
	public function get_all_for_meter( $meter_id, $limit = 50 ) {
		$parcels = Schema::table( 'parcels' );
		$sql     = "SELECT r.*, p.label AS parcel_label
			FROM {$this->table} r
			LEFT JOIN {$parcels} p ON p.id = r.parcel_id
			WHERE r.meter_id = %d
			ORDER BY r.reading_date DESC, r.id DESC
			LIMIT %d";

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

	/**
	 * Build a monthly summary for one meter.
	 *
	 * @param int $meter_id Meter ID.
	 * @return array
	 */
	public function get_monthly_summary_for_meter( $meter_id ) {
		$rows = $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT * FROM {$this->table} WHERE meter_id = %d ORDER BY reading_date ASC, id ASC",
				$meter_id
			)
		); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		if ( empty( $rows ) ) {
			return array();
		}

		$monthly = array();
		$last    = null;

		foreach ( $rows as $row ) {
			$key = gmdate( 'Y-m', strtotime( $row->reading_date ) );

			if ( ! isset( $monthly[ $key ] ) ) {
				$monthly[ $key ] = array(
					'month'        => $key,
					'from_value'   => null,
					'to_value'     => (float) $row->reading_value,
					'consumption'  => 0.0,
					'readings'     => 0,
					'last_date'    => $row->reading_date,
				);
			}

			$monthly[ $key ]['to_value']  = (float) $row->reading_value;
			$monthly[ $key ]['last_date'] = $row->reading_date;
			$monthly[ $key ]['readings']++;

			if ( $last && (float) $row->reading_value >= (float) $last->reading_value ) {
				if ( null === $monthly[ $key ]['from_value'] ) {
					$monthly[ $key ]['from_value'] = (float) $last->reading_value;
				}

				$monthly[ $key ]['consumption'] += (float) $row->reading_value - (float) $last->reading_value;
			}

			$last = $row;
		}

		return array_reverse( array_values( $monthly ) );
	}

	/**
	 * Correct an existing reading (value, date, note).
	 *
	 * @param int   $id   Reading ID.
	 * @param array $data Corrected data: reading_value, reading_date, note.
	 * @return int|false
	 */
	public function update_reading( $id, $data ) {
		return $this->wpdb->update(
			$this->table,
			array(
				'reading_value' => (float) $data['reading_value'],
				'reading_date'  => sanitize_text_field( $data['reading_date'] ),
				'note'          => sanitize_textarea_field( $data['note'] ),
			),
			array( 'id' => absint( $id ) ),
			array( '%f', '%s', '%s' ),
			array( '%d' )
		);
	}
}
