<?php

namespace Fotogenic_AI;

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

final class Service_History {
	private const HISTORY_STATUSES = [ 'queued', 'starting', 'processing', 'paused', 'stopping', 'uncertain', 'partial', 'completed', 'failed', 'stopped', 'removed' ];

    public function handle_runs_list( \WP_REST_Request $request ) {
        $page     = max( 1, (int) $request->get_param( 'page' ) );
        $per_page = max( 1, min( 48, (int) ( $request->get_param( 'perPage' ) ?: 12 ) ) );
		$query     = substr( sanitize_text_field( wp_unslash( (string) $request->get_param( 'query' ) ) ), 0, 200 );
		$status    = sanitize_key( (string) $request->get_param( 'status' ) );
		$status    = in_array( $status, self::HISTORY_STATUSES, true ) ? $status : 'all';
        $user_id  = get_current_user_id();
        if ( $user_id <= 0 ) {
            return rest_ensure_response(
                [
                    'runs'        => [],
                    'totalItems'  => 0,
                    'page'        => $page,
                    'perPage'     => $per_page,
                    'totalPages'  => 1,
					'query'       => $query,
					'status'      => $status,
					'statusCounts'=> [],
                ]
            );
        }

		$queue_history = Store_Queue_Repo::instance()->list_history_queues( $user_id, $page, $per_page, $query, $status );
		$result    = Logger::instance()->list_owned_run_groups( $user_id, $page, $per_page, $query, $status );
        if ( false === ( $result['ready'] ?? true ) ) {
            return new \WP_Error(
                'fotogenic_ai_history_unavailable',
                __( 'Run history is temporarily unavailable. Refresh and try again.', 'fotogenic-ai' ),
                [ 'status' => 503 ]
            );
        }
        $presenter = new Workflow_Response_Factory();
		$runs      = array_map( [ $this, 'present_store_queue' ], (array) ( $queue_history['queues'] ?? [] ) );
		$runs      = array_values( array_filter( $runs ) );
		$suppressed_children = 0;
        foreach ( (array) ( $result['runs'] ?? [] ) as $group ) {
            $bulk_run_uuid = strtolower( trim( (string) ( $group['bulk_run_uuid'] ?? '' ) ) );
            if ( wp_is_uuid( $bulk_run_uuid ) ) {
				if ( Store_Queue_Repo::instance()->bulk_run_belongs_to_queue( $bulk_run_uuid, $user_id ) ) {
					++$suppressed_children;
					continue;
				}
                $run = $this->present_bulk_run_group( $group, $bulk_run_uuid, $user_id );
            } else {
                $run = $this->present_single_run_group( $group, $presenter, $user_id );
            }
            if ( ! empty( $run ) ) {
                $runs[] = $run;
            }
        }
		usort(
			$runs,
			static fn( array $left, array $right ): int => (int) ( $right['sortTimestamp'] ?? 0 ) <=> (int) ( $left['sortTimestamp'] ?? 0 )
		);
		$runs = array_slice( $runs, 0, $per_page );
		foreach ( $runs as &$history_run ) {
			unset( $history_run['sortTimestamp'] );
		}
		unset( $history_run );

		$total       = max( 0, (int) ( $result['total'] ?? 0 ) - $suppressed_children + (int) ( $queue_history['total'] ?? 0 ) );
        $total_pages = max( 1, (int) ceil( $total / $per_page ) );
        return rest_ensure_response(
            [
                'runs'        => $runs,
                'totalItems'  => $total,
                'page'        => $page,
                'perPage'     => $per_page,
                'totalPages'  => $total_pages,
				'query'       => $query,
				'status'      => $status,
				'statusCounts'=> (array) ( $result['status_counts'] ?? [] ),
            ]
        );
    }

	/** @return array<string, mixed> */
	private function present_store_queue( array $queue ): array {
		$queue_uuid = strtolower( trim( (string) ( $queue['queue_uuid'] ?? '' ) ) );
		$status     = $this->normalize_store_queue_history_status( (string) ( $queue['status'] ?? '' ) );
		$selected   = max( 0, (int) ( $queue['selected_count'] ?? 0 ) );
		if ( ! wp_is_uuid( $queue_uuid ) || $selected < 1 ) {
			return [];
		}
		$created_at = (string) ( $queue['created_at'] ?? '' );
		$expected   = max( 1, (int) ( $queue['expected_output_count'] ?? 0 ) );
		return [
			'runId'            => $queue_uuid,
			'workflow'         => 'product_images',
			'sourceType'       => 'product',
			'scope'            => 'store_queue',
			'subjectId'        => 0,
			'label'            => sprintf(
				/* translators: %d: exact Product count in this Store Queue. */
				_n( 'Generate Images · %d Product', 'Generate Images · %d Products', $selected, 'fotogenic-ai' ),
				$selected
			),
			'status'           => $status,
			'createdLabel'     => $this->format_history_datetime( $created_at ),
			'totalOutputs'     => $expected,
			'attachments'      => [],
			'groups'           => [],
			'availableActions' => [],
			'aggregateOnly'    => true,
			'detailAvailable'  => true,
			'message'          => __( 'Open this Product queue to load its current batch and paged Product results.', 'fotogenic-ai' ),
			'resumeIdentity'   => [
				'version'       => 1,
				'workflow'      => 'product_images',
				'sourceType'    => 'product',
				'scope'         => 'store_queue',
				'status'        => $status,
				'queueUuid'     => $queue_uuid,
				'selectedCount' => $selected,
				'expectedOutputCount' => $expected,
				'revision'      => (int) ( $queue['revision'] ?? 1 ),
			],
			'sortTimestamp'    => strtotime( $created_at ) ?: 0,
		];
	}

    /** @return array<string, mixed> */
    private function present_single_run_group( array $group, Workflow_Response_Factory $presenter, int $user_id ): array {
        $run_id = substr( sanitize_text_field( (string) ( $group['sample_run_id'] ?? '' ) ), 0, 64 );
        $row    = Logger::instance()->get_run_row( $run_id );
        if ( empty( $row ) ) {
            return [];
        }
        $presented = $presenter->present_owned_run( $row, $user_id );
        if ( empty( $presented ) ) {
            return [];
        }

        $presented_status = sanitize_key( (string) ( $presented['status'] ?? '' ) );
        $status         = 'removed' === $presented_status
            ? 'removed'
            : $this->normalize_history_status( (string) ( $group['status'] ?? $row['final_status'] ?? '' ) );
        $expected_count = max( 1, min( Workflow_Operational_Limits::MAX_OUTPUTS_PER_ITEM, (int) ( $row['expected_output_count'] ?? 1 ) ) );
        $resume          = [
            'version'             => 1,
            'workflow'            => (string) $presented['workflow'],
            'sourceType'          => (string) $presented['sourceType'],
            'scope'               => 'single',
            'status'              => $status,
            'expectedOutputCount' => $expected_count,
            'subjectId'           => (int) $presented['subjectId'],
            'requestId'           => (string) $presented['requestId'],
            'runId'               => (string) $presented['runId'],
            'jobIds'              => $this->decode_identifier_list( $row['job_ids'] ?? [] ),
        ];
        if ( ! empty( $presented['runItemId'] ) ) {
            $resume['runItemId']       = (string) $presented['runItemId'];
            $resume['campaignGroupId'] = $presented['campaignGroupId'] ?? null;
            $resume['itemOrdinal']     = (int) ( $presented['itemOrdinal'] ?? 0 );
            $resume['targetIdentity']  = (array) ( $presented['targetIdentity'] ?? [] );
        }

        $history_label = $this->workflow_label( (string) $presented['workflow'] );
        $target_identity = is_array( $presented['targetIdentity'] ?? null ) ? $presented['targetIdentity'] : [];
        if ( '' !== trim( (string) ( $target_identity['label'] ?? '' ) ) ) {
            $target_label = (string) $target_identity['label'];
            if ( 'product' === (string) $presented['sourceType'] && '' !== trim( (string) ( $target_identity['sku'] ?? '' ) ) ) {
                $target_label = sprintf(
                    /* translators: 1: exact Product or variation label, 2: SKU. */
                    __( '%1$s · SKU %2$s', 'fotogenic-ai' ),
                    $target_label,
                    (string) $target_identity['sku']
                );
            }
            $history_label = sprintf(
                /* translators: 1: workflow label, 2: exact Product or variation label. */
                __( '%1$s · %2$s', 'fotogenic-ai' ),
                $history_label,
                $target_label
            );
        }

        return array_merge(
            $presented,
            [
                'label'          => $history_label,
                'status'         => $status,
                'createdLabel'   => $this->format_history_datetime( (string) ( $row['started_at'] ?? '' ) ),
                'totalOutputs'   => $expected_count,
                'resumeIdentity' => $resume,
                'aggregateOnly'  => false,
				'sortTimestamp'  => strtotime( (string) ( $row['started_at'] ?? '' ) ) ?: 0,
            ]
        );
    }

    /** @return array<string, mixed> */
    private function present_bulk_run_group( array $group, string $bulk_run_uuid, int $user_id ): array {
        $bulk_run         = Bulk_Repo::instance()->get_run_by_uuid( $bulk_run_uuid, $user_id );
        $detail_available = is_array( $bulk_run );
        $bulk_run         = $detail_available ? $bulk_run : [];
        $workflow         = sanitize_key( (string) ( $bulk_run['workflow'] ?? $group['workflow'] ?? '' ) );
        $source_type     = sanitize_key( (string) ( $group['run_target'] ?? '' ) );
		$selected_count  = max( 1, min( Workflow_Operational_Limits::MAX_RUN_ITEMS, (int) ( $bulk_run['selected_count'] ?? $group['item_count'] ?? 1 ) ) );
        $history_metrics = $this->bulk_history_metrics( $bulk_run, $group, $selected_count );
        $outputs_per_item = $history_metrics['outputsPerItem'];
        $expected_count  = $history_metrics['expectedCount'];
        $status          = $detail_available
            ? $this->normalize_bulk_history_status( (string) ( $bulk_run['status'] ?? '' ), (string) ( $bulk_run['status_reason'] ?? '' ) )
            : $this->normalize_history_status( (string) ( $group['status'] ?? '' ) );
        if ( ! in_array( $workflow, Workflow_Request_Store::WORKFLOWS, true ) || ! in_array( $source_type, [ 'product', 'media', 'post', 'page' ], true ) ) {
            return [];
        }

        $presented = [
            'runId'          => $bulk_run_uuid,
            'workflow'       => $workflow,
            'sourceType'     => $source_type,
            'scope'          => 'bulk',
            'subjectId'      => 0,
            'label'          => sprintf(
                /* translators: 1: workflow label, 2: exact run item count. */
                __( '%1$s · %2$d items', 'fotogenic-ai' ),
                $this->workflow_label( $workflow ),
                $selected_count
            ),
            'status'         => $status,
            'createdLabel'   => $this->format_history_datetime( (string) ( $bulk_run['created_at'] ?? $group['started_at'] ?? '' ) ),
            'totalOutputs'   => $expected_count,
            'attachments'    => [],
            'groups'         => [],
            'availableActions' => [],
            'aggregateOnly'  => true,
            'detailAvailable'=> $detail_available,
			'sortTimestamp'  => strtotime( (string) ( $bulk_run['created_at'] ?? $group['started_at'] ?? '' ) ) ?: 0,
            'message'        => $detail_available
                ? __( 'Open this run to load its exact queue items. Unloaded item details are never fabricated.', 'fotogenic-ai' )
                : __( 'Exact queue item details are no longer available for this historical run. The aggregate status is shown without fabricated rows.', 'fotogenic-ai' ),
        ];
        if ( $detail_available ) {
            $presented['resumeIdentity'] = [
                'version'             => 1,
                'workflow'            => $workflow,
                'sourceType'          => $source_type,
                'scope'               => 'bulk',
                'status'              => $status,
                'expectedOutputCount' => $expected_count,
                'runUuid'             => $bulk_run_uuid,
                'selectedCount'       => $selected_count,
            ];
            if ( $outputs_per_item > 0 ) {
                $presented['resumeIdentity']['outputsPerItem'] = $outputs_per_item;
            }
        }
        return $presented;
    }

    /** @return array{expectedCount:int,outputsPerItem:int} */
    private function bulk_history_metrics( array $bulk_run, array $group, int $selected_count ): array {
        $expected_count = 0;
        $snapshot = $bulk_run['filters_snapshot'] ?? null;
        if ( is_string( $snapshot ) ) {
            $decoded  = json_decode( $snapshot, true );
            $snapshot = is_array( $decoded ) && JSON_ERROR_NONE === json_last_error() ? $decoded : null;
        }
        if ( is_array( $snapshot ) ) {
            $expected_count = is_int( $snapshot['acceptedAuthority']['totalOutputs'] ?? null )
				? $snapshot['acceptedAuthority']['totalOutputs']
				: 0;
        } else {
            $expected_count = (int) ( $group['expected_output_count'] ?? 0 );
        }
        $maximum = (int) ( $group['outputs_per_item'] ?? 0 );
        if ( ! is_array( $snapshot ) && $expected_count <= 0 && $maximum > 0 ) {
            $expected_count = $selected_count * $maximum;
        }
		$expected_count = max( 0, min( Workflow_Operational_Limits::MAX_RUN_OUTPUTS, $expected_count ) );

        $minimum   = (int) ( $group['min_outputs_per_item'] ?? 0 );
        $item_count = (int) ( $group['item_count'] ?? 0 );
        $uniform = $item_count === $selected_count
            && $minimum > 0
            && $minimum === $maximum
            && $expected_count === $selected_count * $maximum;

        return [
            'expectedCount'  => $expected_count,
            'outputsPerItem' => $uniform ? max( 1, min( 10, $maximum ) ) : 0,
        ];
    }

    private function normalize_history_status( string $status ): string {
        $status = sanitize_key( $status );
        return in_array( $status, [ 'starting', 'processing', 'uncertain', 'partial', 'completed', 'failed', 'stopped', 'removed' ], true )
            ? $status
            : 'uncertain';
    }

    private function normalize_bulk_history_status( string $status, string $reason ): string {
        $status = sanitize_key( $status );
        if ( 'completed' === $status && 'partial' === sanitize_key( $reason ) ) {
            return 'partial';
	}

		$statuses = [
			'admitting' => 'starting',
			'running'   => 'processing',
			'paused'    => 'paused',
			'stopping'  => 'stopping',
			'stopped'   => 'stopped',
			'completed' => 'completed',
			'failed'    => 'failed',
		];

		return $statuses[ $status ] ?? 'uncertain';
    }

	private function normalize_store_queue_history_status( string $status ): string {
		$statuses = [
			'preparing' => 'queued',
			'reviewing' => 'queued',
			'ready'     => 'queued',
			'running'   => 'processing',
			'paused'    => 'paused',
			'stopping'  => 'stopping',
			'completed' => 'completed',
			'partial'   => 'partial',
			'failed'    => 'failed',
			'stopped'   => 'stopped',
			'uncertain' => 'uncertain',
			'expired'   => 'stopped',
		];
		return $statuses[ sanitize_key( $status ) ] ?? 'uncertain';
	}

    /**
     * @param mixed $value Candidate value.
     * @return string[]
     */
    private function decode_identifier_list( $value ): array {
        if ( is_string( $value ) ) {
            $decoded = json_decode( $value, true );
            $value   = is_array( $decoded ) ? $decoded : [];
        }
        if ( ! is_array( $value ) ) {
            return [];
        }
        $identifiers = [];
        foreach ( $value as $candidate ) {
            if ( ! is_scalar( $candidate ) ) {
                continue;
            }
            $identifier = substr( sanitize_text_field( (string) $candidate ), 0, 64 );
            if ( '' !== $identifier && ! in_array( $identifier, $identifiers, true ) ) {
                $identifiers[] = $identifier;
            }
            if ( count( $identifiers ) >= 10 ) {
                break;
            }
        }
        return $identifiers;
    }

    private function format_history_datetime( string $timestamp ): string {
        $parsed = strtotime( $timestamp );
        if ( false === $parsed ) {
            return '';
        }
        return wp_date(
            get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' ),
            $parsed
        );
    }

    private function workflow_label( string $workflow ): string {
		$labels = [
			'campaign_set'        => __( 'Campaign Set result', 'fotogenic-ai' ),
			'increase_resolution' => __( 'Increase Resolution result', 'fotogenic-ai' ),
		];

		return $labels[ sanitize_key( $workflow ) ] ?? __( 'Generate Images result', 'fotogenic-ai' );
    }

}
