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

namespace KGV\VereinManager\Repositories;

use KGV\VereinManager\Schema;

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

class CostRepository extends AbstractRepository {

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

	/**
	 * Get the persistent year table name.
	 *
	 * @return string
	 */
	private function year_table() {
		return Schema::table( 'cost_years' );
	}

	/**
	 * Get the yearly section rates table name.
	 *
	 * @return string
	 */
	private function rate_table() {
		return Schema::table( 'cost_rates' );
	}

	/**
	 * Search cost entries.
	 *
	 * @param array $args Query arguments.
	 * @return array
	 */
	public function search( $args = array() ) {
		$search  = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
		$year    = isset( $args['year'] ) ? absint( $args['year'] ) : 0;
		$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'name', array( 'entry_year', 'name', 'distribution_type', 'unit_amount', 'total_cost', 'updated_at', 'created_at' ), 'name' );
		$order   = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );

		$sql    = "SELECT * FROM {$this->table} WHERE 1=1";
		$params = array();

		if ( $year > 0 ) {
			$this->ensure_year( $year );
			$sql     .= ' AND entry_year = %d';
			$params[] = $year;
		}

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

		$sql .= " ORDER BY {$orderby} {$order}, 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 a cost entry.
	 *
	 * @param array $data Entry data.
	 * @param int   $id   Optional ID.
	 * @return int|false
	 */
	public function save( $data, $id = 0 ) {
		$payload = array(
			'entry_year'        => absint( $data['entry_year'] ),
			'name'              => $data['name'],
			'distribution_type' => isset( $data['distribution_type'] ) ? $data['distribution_type'] : 'parcel',
			'unit_amount'       => isset( $data['unit_amount'] ) ? (float) $data['unit_amount'] : 0,
			'total_cost'        => (float) $data['total_cost'],
			'is_mandatory'      => isset( $data['is_mandatory'] ) ? (int) (bool) $data['is_mandatory'] : 1,
			'note'              => $data['note'],
			'updated_at'        => $this->now(),
		);

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

		$this->ensure_year( $payload['entry_year'] );

		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( '%d', '%s', '%s', '%f', '%f', '%d', '%s', '%s', '%s' ) );

		return $this->wpdb->insert_id;
	}

	/**
	 * Get available years for filters and dropdowns.
	 *
	 * @param int $selected_year Optional selected year.
	 * @return array
	 */
	public function get_years( $selected_year = 0 ) {
		$year_table   = $this->year_table();
		$years        = $this->wpdb->get_col( "SELECT DISTINCT entry_year FROM {$this->table} WHERE entry_year > 0 ORDER BY entry_year DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
		$saved_years  = $this->wpdb->get_col( "SELECT entry_year FROM {$year_table} ORDER BY entry_year DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
		$current_year = (int) current_time( 'Y' );
		$years        = array_map( 'absint', array_merge( (array) $years, (array) $saved_years ) );
		$years[]      = $current_year;
		$years[]      = $current_year - 1;
		$years[]      = $current_year + 1;

		if ( $selected_year > 0 ) {
			$years[] = absint( $selected_year );
		}

		$years = array_values( array_unique( array_filter( $years ) ) );
		rsort( $years, SORT_NUMERIC );

		return $years;
	}

	/**
	 * Ensure one year exists so it remains selectable without cost entries.
	 *
	 * @param int $year Year value.
	 * @return bool
	 */
	public function ensure_year( $year ) {
		$year       = absint( $year );
		$year_table = $this->year_table();

		if ( $year < 1 || '' === $year_table ) {
			return false;
		}

		$exists = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(*) FROM {$year_table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		if ( $exists > 0 ) {
			return true;
		}

		$result = $this->wpdb->insert(
			$year_table,
			array(
				'entry_year'          => $year,
				'power_price_per_kwh' => null,
				'water_price_per_m3'  => null,
				'created_at'          => $this->now(),
				'updated_at'          => $this->now(),
			),
			array( '%d', '%s', '%s', '%s', '%s' )
		);

		return false !== $result;
	}

	/**
	 * Save the configured yearly prices for electricity and water.
	 *
	 * @param int        $year                 Year value.
	 * @param float|null $power_price_per_kwh  Price per kWh.
	 * @param float|null $water_price_per_m3   Price per m³.
	 * @return bool
	 */
	public function save_year( $year, $power_price_per_kwh = null, $water_price_per_m3 = null ) {
		$year       = absint( $year );
		$year_table = $this->year_table();

		if ( ! $this->ensure_year( $year ) ) {
			return false;
		}

		$result = $this->wpdb->update(
			$year_table,
			array(
				'power_price_per_kwh' => null !== $power_price_per_kwh ? (float) $power_price_per_kwh : null,
				'water_price_per_m3'  => null !== $water_price_per_m3 ? (float) $water_price_per_m3 : null,
				'updated_at'          => $this->now(),
			),
			array( 'entry_year' => $year ),
			array( '%f', '%f', '%s' ),
			array( '%d' )
		);

		return false !== $result;
	}

	/**
	 * Load one year's saved price details.
	 *
	 * @param int $year Selected year.
	 * @return object|null
	 */
	public function get_year_details( $year ) {
		$year       = absint( $year );
		$year_table = $this->year_table();

		if ( $year < 1 || '' === $year_table ) {
			return null;
		}

		return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$year_table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Get lock state for one statement year.
	 *
	 * @param int $year Selected year.
	 * @return array
	 */
	public function get_statement_lock_state( $year ) {
		$details = $this->get_year_details( $year );

		if ( ! $details ) {
			return array(
				'is_locked' => false,
				'locked_at' => null,
				'locked_by' => 0,
			);
		}

		return array(
			'is_locked' => ! empty( $details->statement_is_locked ) && 1 === (int) $details->statement_is_locked,
			'locked_at' => isset( $details->statement_locked_at ) ? $details->statement_locked_at : null,
			'locked_by' => isset( $details->statement_locked_by ) ? (int) $details->statement_locked_by : 0,
		);
	}

	/**
	 * Check whether statement data for one year is locked.
	 *
	 * @param int $year Selected year.
	 * @return bool
	 */
	public function is_statement_locked( $year ) {
		$state = $this->get_statement_lock_state( $year );

		return ! empty( $state['is_locked'] );
	}

	/**
	 * Lock or unlock a statement year.
	 *
	 * @param int  $year       Selected year.
	 * @param bool $is_locked  Target lock state.
	 * @param int  $locked_by  User ID that changes the state.
	 * @return bool
	 */
	public function set_statement_lock( $year, $is_locked, $locked_by = 0 ) {
		$year = absint( $year );

		if ( $year < 1 || ! $this->ensure_year( $year ) ) {
			return false;
		}

		$payload = array(
			'statement_is_locked' => $is_locked ? 1 : 0,
			'statement_locked_at' => $is_locked ? $this->now() : null,
			'statement_locked_by' => $is_locked ? absint( $locked_by ) : 0,
			'updated_at'          => $this->now(),
		);

		$result = $this->wpdb->update(
			$this->year_table(),
			$payload,
			array( 'entry_year' => $year ),
			array( '%d', '%s', '%d', '%s' ),
			array( '%d' )
		);

		return false !== $result;
	}

	/**
	 * Save section-specific yearly prices.
	 *
	 * @param array $data Section pricing data.
	 * @return bool
	 */
	public function save_section_prices( $data ) {
		$year       = absint( $data['entry_year'] );
		$section_id = absint( $data['section_id'] );
		$rate_table = $this->rate_table();

		if ( ! $this->ensure_year( $year ) || $section_id < 1 || '' === $rate_table ) {
			return false;
		}

		$payload = array(
			'entry_year'          => $year,
			'section_id'          => $section_id,
			'power_price_per_kwh' => null !== $data['power_price_per_kwh'] ? (float) $data['power_price_per_kwh'] : null,
			'water_price_per_m3'  => null !== $data['water_price_per_m3'] ? (float) $data['water_price_per_m3'] : null,
			'updated_at'          => $this->now(),
		);

		$exists = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(*) FROM {$rate_table} WHERE entry_year = %d AND section_id = %d", $year, $section_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		if ( $exists > 0 ) {
			$result = $this->wpdb->update(
				$rate_table,
				array(
					'power_price_per_kwh' => $payload['power_price_per_kwh'],
					'water_price_per_m3'  => $payload['water_price_per_m3'],
					'updated_at'          => $payload['updated_at'],
				),
				array(
					'entry_year' => $year,
					'section_id' => $section_id,
				),
				array( '%f', '%f', '%s' ),
				array( '%d', '%d' )
			);

			return false !== $result;
		}

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

		return false !== $result;
	}

	/**
	 * Get all saved section prices for one year.
	 *
	 * @param int $year Selected year.
	 * @return array
	 */
	public function get_section_prices( $year ) {
		$year      = absint( $year );
		$rate_table = $this->rate_table();
		$sections   = Schema::table( 'sections' );

		if ( $year < 1 || '' === $rate_table ) {
			return array();
		}

		$sql = "SELECT r.*, s.name AS section_name
			FROM {$rate_table} r
			LEFT JOIN {$sections} s ON s.id = r.section_id
			WHERE r.entry_year = %d
			ORDER BY s.name ASC, r.section_id ASC";

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

	/**
	 * Sum all costs for a year.
	 *
	 * @param int $year Selected year.
	 * @return float
	 */
	public function get_total_for_year( $year ) {
		if ( $year < 1 ) {
			return 0.0;
		}

		$total = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COALESCE(SUM(total_cost), 0) FROM {$this->table} WHERE entry_year = %d", absint( $year ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		return (float) $total;
	}

	// -------------------------------------------------------------------------
	// Parcel cost assignments
	// -------------------------------------------------------------------------

	/**
	 * Get the parcel cost assignments table name.
	 *
	 * @return string
	 */
	private function assignment_table() {
		return Schema::table( 'parcel_cost_assignments' );
	}

	/**
	 * Get cost entry IDs assigned to a specific parcel.
	 *
	 * @param int $parcel_id Parcel ID.
	 * @return int[]
	 */
	public function get_assigned_entry_ids( $parcel_id ) {
		$parcel_id = absint( $parcel_id );
		$table     = $this->assignment_table();

		if ( $parcel_id < 1 || '' === $table ) {
			return array();
		}

		$ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT cost_entry_id FROM {$table} WHERE parcel_id = %d", $parcel_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		return array_map( 'intval', (array) $ids );
	}

	/**
	 * Get all cost entry IDs that have at least one parcel assignment.
	 *
	 * @param int $year Optional – filter by entry year (0 = all years).
	 * @return int[]
	 */
	public function get_entry_ids_with_assignments( $year = 0 ) {
		$table = $this->assignment_table();

		if ( '' === $table ) {
			return array();
		}

		$year = absint( $year );

		if ( $year > 0 ) {
			$ids = $this->wpdb->get_col( $this->wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
				"SELECT DISTINCT a.cost_entry_id FROM {$table} a
				 INNER JOIN {$this->table} e ON e.id = a.cost_entry_id
				 WHERE e.entry_year = %d",
				$year
			) );
		} else {
			$ids = $this->wpdb->get_col( "SELECT DISTINCT cost_entry_id FROM {$table}" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
		}

		return array_map( 'intval', (array) $ids );
	}

	/**
	 * Assign a cost entry to a parcel.
	 *
	 * @param int $parcel_id     Parcel ID.
	 * @param int $cost_entry_id Cost entry ID.
	 * @return bool
	 */
	public function assign_to_parcel( $parcel_id, $cost_entry_id ) {
		$parcel_id     = absint( $parcel_id );
		$cost_entry_id = absint( $cost_entry_id );
		$table         = $this->assignment_table();

		if ( $parcel_id < 1 || $cost_entry_id < 1 || '' === $table ) {
			return false;
		}

		$exists = (int) $this->wpdb->get_var( $this->wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			"SELECT COUNT(*) FROM {$table} WHERE parcel_id = %d AND cost_entry_id = %d",
			$parcel_id,
			$cost_entry_id
		) );

		if ( $exists > 0 ) {
			return true;
		}

		$result = $this->wpdb->insert(
			$table,
			array(
				'parcel_id'     => $parcel_id,
				'cost_entry_id' => $cost_entry_id,
				'created_at'    => $this->now(),
			),
			array( '%d', '%d', '%s' )
		);

		return false !== $result;
	}

	/**
	 * Remove a cost entry assignment from a parcel.
	 *
	 * @param int $parcel_id     Parcel ID.
	 * @param int $cost_entry_id Cost entry ID.
	 * @return bool
	 */
	public function unassign_from_parcel( $parcel_id, $cost_entry_id ) {
		$parcel_id     = absint( $parcel_id );
		$cost_entry_id = absint( $cost_entry_id );
		$table         = $this->assignment_table();

		if ( $parcel_id < 1 || $cost_entry_id < 1 || '' === $table ) {
			return false;
		}

		$result = $this->wpdb->delete(
			$table,
			array(
				'parcel_id'     => $parcel_id,
				'cost_entry_id' => $cost_entry_id,
			),
			array( '%d', '%d' )
		);

		return false !== $result;
	}

	/**
	 * Remove all parcel assignments for a cost entry (e.g. when the entry is deleted).
	 *
	 * @param int $cost_entry_id Cost entry ID.
	 * @return void
	 */
	public function delete_assignments_for_entry( $cost_entry_id ) {
		$table = $this->assignment_table();

		if ( '' !== $table && absint( $cost_entry_id ) > 0 ) {
			$this->wpdb->delete( $table, array( 'cost_entry_id' => absint( $cost_entry_id ) ), array( '%d' ) );
		}
	}
}
