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

namespace KGV\VereinManager\Repositories;

use KGV\VereinManager\Schema;

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

class WorkRepository extends AbstractRepository {

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

	/**
	 * Jobs table.
	 *
	 * @return string
	 */
	private function jobs_table() {
		return Schema::table( 'work_jobs' );
	}

	/**
	 * Year config table.
	 *
	 * @return string
	 */
	private function year_config_table() {
		return Schema::table( 'work_year_config' );
	}

	/**
	 * Log members table.
	 *
	 * @return string
	 */
	private function members_table() {
		return Schema::table( 'work_log_members' );
	}

	// -------------------------------------------------------------------------
	// Jobs
	// -------------------------------------------------------------------------

	/**
	 * Get all jobs.
	 *
	 * @return array
	 */
	public function get_jobs() {
		$table = $this->jobs_table();
		return $this->wpdb->get_results( "SELECT * FROM {$table} ORDER BY name ASC" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
	}

	/**
	 * Get a single job by ID.
	 *
	 * @param int $id Job ID.
	 * @return object|null
	 */
	public function find_job( $id ) {
		$table = $this->jobs_table();
		return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", absint( $id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Save or update a job.
	 *
	 * @param array $data Job data.
	 * @param int   $id   Optional ID.
	 * @return int|false
	 */
	public function save_job( $data, $id = 0 ) {
		$table   = $this->jobs_table();
		$payload = array(
			'name'        => sanitize_text_field( $data['name'] ),
			'description' => isset( $data['description'] ) ? sanitize_textarea_field( $data['description'] ) : '',
			'updated_at'  => $this->now(),
		);
		$formats = array( '%s', '%s', '%s' );

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

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

	/**
	 * Delete a job and related log members/logs.
	 *
	 * @param int $id Job ID.
	 * @return bool
	 */
	public function delete_job( $id ) {
		$id          = absint( $id );
		$logs_table  = $this->table;
		$mem_table   = $this->members_table();
		$jobs_table  = $this->jobs_table();

		// Delete members of all logs for this job.
		$log_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT id FROM {$logs_table} WHERE job_id = %d", $id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		foreach ( array_map( 'absint', (array) $log_ids ) as $log_id ) {
			$this->wpdb->delete( $mem_table, array( 'log_id' => $log_id ), array( '%d' ) );
		}
		// Delete logs.
		$this->wpdb->delete( $logs_table, array( 'job_id' => $id ), array( '%d' ) );
		// Delete job.
		$result = $this->wpdb->delete( $jobs_table, array( 'id' => $id ), array( '%d' ) );
		return false !== $result;
	}

	/**
	 * Check if a job name already exists (for uniqueness).
	 *
	 * @param string $name Job name.
	 * @param int    $exclude_id Exclude this ID.
	 * @return bool
	 */
	public function job_name_exists( $name, $exclude_id = 0 ) {
		$table = $this->jobs_table();
		$sql   = $this->wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE name = %s", sanitize_text_field( $name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		if ( $exclude_id > 0 ) {
			$sql .= $this->wpdb->prepare( ' AND id != %d', absint( $exclude_id ) );
		}
		return (int) $this->wpdb->get_var( $sql ) > 0; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	// -------------------------------------------------------------------------
	// Year config
	// -------------------------------------------------------------------------

	/**
	 * Get config for a specific year (creates if missing).
	 *
	 * @param int $year Year.
	 * @return object|null
	 */
	public function get_year_config( $year ) {
		$year  = absint( $year );
		$table = $this->year_config_table();
		$row   = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		return $row;
	}

	/**
	 * Get all available configured years.
	 *
	 * @param int $selected_year Optional selected year to always include.
	 * @return array
	 */
	public function get_years( $selected_year = 0 ) {
		$config_table = $this->year_config_table();
		$log_table    = $this->table;
		$current_year = (int) current_time( 'Y' );

		$config_years = $this->wpdb->get_col( "SELECT entry_year FROM {$config_table} ORDER BY entry_year DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
		$log_years    = $this->wpdb->get_col( "SELECT DISTINCT YEAR(work_date) FROM {$log_table}" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery

		$years   = array_map( 'absint', array_merge( (array) $config_years, (array) $log_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;
	}

	/**
	 * Save year configuration (upsert).
	 *
	 * @param int   $year                  Year.
	 * @param float $required_hours        Required hours per member.
	 * @param float $price_per_missing_hour Price per missing hour in €.
	 * @return bool
	 */
	public function save_year_config( $year, $required_hours, $price_per_missing_hour ) {
		$year                  = absint( $year );
		$required_hours        = max( 0, (float) $required_hours );
		$price_per_missing_hour = max( 0, (float) $price_per_missing_hour );
		$table                 = $this->year_config_table();
		$existing              = $this->get_year_config( $year );

		if ( $existing ) {
			$result = $this->wpdb->update(
				$table,
				array(
					'required_hours'         => $required_hours,
					'price_per_missing_hour' => $price_per_missing_hour,
					'updated_at'             => $this->now(),
				),
				array( 'entry_year' => $year ),
				array( '%f', '%f', '%s' ),
				array( '%d' )
			);
			return false !== $result;
		}

		$result = $this->wpdb->insert(
			$table,
			array(
				'entry_year'             => $year,
				'required_hours'         => $required_hours,
				'price_per_missing_hour' => $price_per_missing_hour,
				'created_at'             => $this->now(),
				'updated_at'             => $this->now(),
			),
			array( '%d', '%f', '%f', '%s', '%s' )
		);
		return false !== $result;
	}

	// -------------------------------------------------------------------------
	// Work logs
	// -------------------------------------------------------------------------

	/**
	 * Search work logs.
	 *
	 * @param array $args Query arguments (year, user_id, job_id, s, orderby, order).
	 * @return array
	 */
	public function search_logs( $args = array() ) {
		$year    = isset( $args['year'] ) ? absint( $args['year'] ) : 0;
		$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : 0;
		$job_id  = isset( $args['job_id'] ) ? absint( $args['job_id'] ) : 0;
		$search  = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
		$orderby = $this->sanitize_orderby(
			isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'work_date',
			array( 'work_date', 'job_name', 'updated_at', 'created_at' ),
			'work_date'
		);
		$order   = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'DESC' );

		$logs_table = $this->table;
		$jobs_table = $this->jobs_table();
		$mem_table  = $this->members_table();

		$sql    = "SELECT l.*, COALESCE(j.name, '') AS job_name FROM {$logs_table} l
		           LEFT JOIN {$jobs_table} j ON j.id = l.job_id WHERE 1=1";
		$params = array();

		if ( $year > 0 ) {
			$sql     .= ' AND YEAR(l.work_date) = %d';
			$params[] = $year;
		}

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

		if ( $user_id > 0 ) {
			$sql     .= " AND l.id IN (SELECT log_id FROM {$mem_table} WHERE user_id = %d)";
			$params[] = $user_id;
		}

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

		$sql .= " ORDER BY l.{$orderby} {$order}, l.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
	}

	/**
	 * Get a work log with its member assignments.
	 *
	 * @param int $id Log ID.
	 * @return object|null
	 */
	public function find_log( $id ) {
		$row = $this->find( $id );
		if ( $row ) {
			$row->members = $this->get_log_members( $id );
		}
		return $row;
	}

	/**
	 * Save or update a work log including member assignments.
	 *
	 * @param array $data    Log data.
	 * @param int   $id      Optional log ID.
	 * @param array $members Array of [user_id => hours].
	 * @return int|false
	 */
	public function save_log( $data, $id = 0, $members = array() ) {
		$payload = array(
			'job_id'     => absint( $data['job_id'] ),
			'work_date'  => sanitize_text_field( $data['work_date'] ),
			'note'       => isset( $data['note'] ) ? sanitize_textarea_field( $data['note'] ) : '',
			'updated_at' => $this->now(),
		);
		$formats = array( '%d', '%s', '%s', '%s' );

		if ( $id > 0 ) {
			$this->wpdb->update( $this->table, $payload, array( 'id' => $id ), $formats, array( '%d' ) );
			$log_id = $id;
		} else {
			$payload['created_at'] = $this->now();
			$this->wpdb->insert( $this->table, $payload, array( '%d', '%s', '%s', '%s', '%s' ) );
			$log_id = $this->wpdb->insert_id;
		}

		if ( $log_id && is_array( $members ) ) {
			$this->sync_log_members( $log_id, $members );
		}

		return $log_id;
	}

	/**
	 * Delete a work log and its member assignments.
	 *
	 * @param int $id Log ID.
	 * @return bool
	 */
	public function delete_log( $id ) {
		$id = absint( $id );
		$this->wpdb->delete( $this->members_table(), array( 'log_id' => $id ), array( '%d' ) );
		$result = $this->wpdb->delete( $this->table, array( 'id' => $id ), array( '%d' ) );
		return false !== $result;
	}

	// -------------------------------------------------------------------------
	// Log members
	// -------------------------------------------------------------------------

	/**
	 * Get member assignments for a log.
	 *
	 * @param int $log_id Log ID.
	 * @return array
	 */
	public function get_log_members( $log_id ) {
		$table = $this->members_table();
		return $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$table} WHERE log_id = %d", absint( $log_id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Sync member assignments for a log.
	 *
	 * @param int   $log_id  Log ID.
	 * @param array $members Array of [user_id => hours].
	 * @return void
	 */
	public function sync_log_members( $log_id, $members ) {
		$log_id    = absint( $log_id );
		$table     = $this->members_table();
		$now       = $this->now();

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

		foreach ( $members as $user_id => $hours ) {
			$user_id = absint( $user_id );
			$hours   = max( 0, (float) $hours );
			if ( $user_id < 1 ) {
				continue;
			}
			$this->wpdb->insert(
				$table,
				array(
					'log_id'     => $log_id,
					'user_id'    => $user_id,
					'hours'      => $hours,
					'created_at' => $now,
				),
				array( '%d', '%d', '%f', '%s' )
			);
		}
	}

	// -------------------------------------------------------------------------
	// Summary / statistics
	// -------------------------------------------------------------------------

	/**
	 * Get completed hours per member for a given year.
	 *
	 * @param int $year Year.
	 * @return array Array keyed by user_id with total hours.
	 */
	public function get_hours_per_member( $year ) {
		$year      = absint( $year );
		$log_table = $this->table;
		$mem_table = $this->members_table();

		$rows = $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT m.user_id, SUM(m.hours) AS total_hours
				 FROM {$mem_table} m
				 INNER JOIN {$log_table} l ON l.id = m.log_id
				 WHERE YEAR(l.work_date) = %d
				 GROUP BY m.user_id", // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
				$year
			)
		);

		$result = array();
		foreach ( $rows as $row ) {
			$result[ absint( $row->user_id ) ] = (float) $row->total_hours;
		}
		return $result;
	}

	/**
	 * Build per-member work summary for a year.
	 *
	 * Returns array of objects with user_id, display_name, completed_hours,
	 * required_hours, missing_hours, surcharge.
	 *
	 * @param int   $year        Year.
	 * @param array $all_members Array of WP_User objects.
	 * @return array
	 */
	public function get_member_summary( $year, $all_members ) {
		$config         = $this->get_year_config( $year );
		$required_hours = $config ? (float) $config->required_hours : 0.0;
		$price          = $config ? (float) $config->price_per_missing_hour : 0.0;
		$done_map       = $this->get_hours_per_member( $year );

		$summary = array();
		foreach ( $all_members as $member ) {
			$uid             = (int) $member->ID;
			$completed       = isset( $done_map[ $uid ] ) ? $done_map[ $uid ] : 0.0;
			$missing         = max( 0.0, $required_hours - $completed );
			$surcharge       = round( $missing * $price, 2 );

			$obj                  = new \stdClass();
			$obj->user_id         = $uid;
			$obj->display_name    = $member->display_name;
			$obj->completed_hours = $completed;
			$obj->required_hours  = $required_hours;
			$obj->missing_hours   = $missing;
			$obj->surcharge       = $surcharge;

			$summary[] = $obj;
		}
		return $summary;
	}
}
