<?php

namespace Fotogenic_AI;

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

final class Service_Generate_Active {
    private const WORKFLOWS = [ 'product_images', 'campaign_set', 'increase_resolution' ];

    /** @return \WP_REST_Response|\WP_Error */
    public function active( int $user_id, string $target, int $subject_id, string $workflow, string $request_id ) {
        $target = sanitize_key( $target );
        if ( ! in_array( $target, [ 'product', 'media', 'post', 'page' ], true ) ) {
            return $this->empty_response();
        }
        $workflow = sanitize_key( $workflow );
        if ( ! in_array( $workflow, self::WORKFLOWS, true ) ) {
            return $this->empty_response();
        }
        if ( 'product_images' !== $workflow && ! in_array( $target, [ 'product', 'media' ], true ) ) {
            return $this->empty_response();
        }
        $request_id = strtolower( trim( $request_id ) );
        if ( ! wp_is_uuid( $request_id ) ) {
            return $this->empty_response();
        }
        if ( $user_id <= 0 || $subject_id <= 0 ) {
            return $this->empty_response();
        }

        $job_manager = Job_Manager::instance();
        $active_job_key = $this->get_active_job_key( $job_manager, $workflow, $target, $subject_id, $user_id, $request_id );
        $active_job = '' !== $active_job_key ? $job_manager->get_active_job_by_key( $user_id, $active_job_key ) : null;
        if ( ! is_array( $active_job ) ) {
            $active_job = $job_manager->get_active_workflow_job( $user_id, $workflow, $target, $subject_id, $request_id );
        }
        $active_run = [];
        if ( is_array( $active_job ) ) {
            $run_id = trim( (string) ( $active_job['run_id'] ?? '' ) );
            $active_run = '' !== $run_id ? Logger::instance()->get_run_row( $run_id ) : [];
            if ( ! $this->active_job_matches_request( $active_job, $active_run, $user_id, $workflow, $target, $subject_id, $request_id ) ) {
                $active_job = null;
                $active_run = [];
            }
        }
        if ( is_array( $active_job ) ) {
            $status = (string) ( $active_run['final_status'] ?? '' );
            if ( ! $this->is_active_status( $status ) ) {
                $run_id = (string) ( $active_job['run_id'] ?? '' );
                if ( '' !== $run_id ) {
                    $job_manager->clear_active_jobs_by_run( $user_id, $run_id );
                } elseif ( '' !== $active_job_key ) {
                    $job_manager->clear_active_job( $user_id, $active_job_key );
                }
                $active_job = null;
            } else {
                $authorized = $this->authorize_current_run( $active_run, $target, $subject_id );
                if ( is_wp_error( $authorized ) ) {
                    return $authorized;
                }
                return $this->format_response( $this->build_active_from_run( $active_run ), true );
            }
        }

        $latest_run = Logger::instance()->get_latest_workflow_run_for_subject( $user_id, $workflow, $target, $subject_id );
        if ( ! $this->run_matches_request( $latest_run, $user_id, $workflow, $target, $subject_id, $request_id ) ) {
            return $this->empty_response();
        }
        $status = (string) ( $latest_run['final_status'] ?? '' );
        if ( ! $this->is_known_status( $status ) ) {
            return $this->empty_response();
        }
        $authorized = $this->authorize_current_run( $latest_run, $target, $subject_id );
        if ( is_wp_error( $authorized ) ) {
            return $authorized;
        }

        $recovered = $this->build_active_from_run( $latest_run );
        $job_ids = $this->normalize_job_ids( $recovered['job_ids'] ?? [] );
        $uncertain_no_job = $this->is_active_status( $status )
            && empty( $job_ids )
            && ! empty( $recovered['uncertain'] );
        $is_active = $this->is_active_status( $status ) && ( ! empty( $job_ids ) || $uncertain_no_job );
        if ( $is_active && ! empty( $job_ids ) ) {
            $recovered_key = $this->get_active_job_key(
                $job_manager,
                $workflow,
                $target,
                $subject_id,
                $user_id,
                (string) ( $latest_run['request_id'] ?? '' )
            );
            if ( '' !== $recovered_key ) {
                $job_manager->set_active_job( $user_id, (string) $job_ids[0], $recovered, $recovered_key );
            }
        }
        return $this->format_response( $recovered, $is_active );
    }

    private function get_active_job_key( Job_Manager $job_manager, string $workflow, string $target, int $subject_id, int $user_id, string $request_id ): string {
        if ( 'product_images' === $workflow ) {
            return $job_manager->get_subject_job_key( $target, $subject_id );
        }
        return $job_manager->get_workflow_job_key( $workflow, $target, $subject_id, $user_id, $request_id );
    }

    private function active_job_matches_request( array $active, array $row, int $user_id, string $workflow, string $target, int $subject_id, string $request_id ): bool {
        if (
            $workflow !== (string) ( $active['workflow'] ?? '' )
            || $target !== (string) ( $active['run_target'] ?? '' )
            || $subject_id !== (int) ( $active['subject_id'] ?? 0 )
            || (int) ( $active['user_id'] ?? 0 ) !== $user_id
            || ! hash_equals( $request_id, (string) ( $active['request_id'] ?? '' ) )
        ) {
            return false;
        }
        $run_id = trim( (string) ( $active['run_id'] ?? '' ) );
        if ( '' === $run_id ) {
            return false;
        }
        if ( ! $this->run_matches_request( $row, $user_id, $workflow, $target, $subject_id, $request_id ) ) {
            return false;
        }
        if ( ! hash_equals( (string) ( $row['request_id'] ?? '' ), (string) ( $active['request_id'] ?? '' ) ) ) {
            return false;
        }
        $active_job_ids = $this->normalize_job_ids( $active['job_ids'] ?? [] );
        $run_job_ids = $this->normalize_job_ids( $row['job_ids'] ?? [] );
        return ! empty( $active_job_ids ) && empty( array_diff( $active_job_ids, $run_job_ids ) );
    }

    /** @return bool|\WP_Error */
    private function authorize_current_run( array $row, string $target, int $subject_id ) {
        if ( 'product' === $target ) {
            if (
                ! function_exists( __NAMESPACE__ . '\\fotogenic_ai_woocommerce_available' )
                || ! fotogenic_ai_woocommerce_available()
            ) {
                return Messages::error( 'woocommerce_not_active', 503 );
            }
            if ( ! current_user_can( 'edit_post', $subject_id ) ) {
                return $this->forbidden_response();
            }
            if ( ! is_object( wc_get_product( $subject_id ) ) ) {
                return $this->unavailable_response();
            }
        } elseif ( 'media' === $target ) {
            $subject_authorized = $this->authorize_image_attachment( $subject_id );
            if ( is_wp_error( $subject_authorized ) ) {
                return $subject_authorized;
            }
        } else {
            $primitive_capability = 'post' === $target ? 'edit_posts' : 'edit_pages';
            if ( ! current_user_can( $primitive_capability ) || ! current_user_can( 'edit_post', $subject_id ) ) {
                return $this->forbidden_response();
            }
            $subject = get_post( $subject_id );
            if ( ! ( $subject instanceof \WP_Post ) || $target !== $subject->post_type ) {
                return $this->unavailable_response();
            }
        }

        $source_attachment_ids = $this->decode_id_list( $row['source_attachment_ids'] ?? [] );
        $primary_source_id = absint( $row['source_attachment_id'] ?? 0 );
        if ( $primary_source_id > 0 ) {
            array_unshift( $source_attachment_ids, $primary_source_id );
        }

        $job_ids = $this->normalize_job_ids( $row['job_ids'] ?? [] );
		$attachments_map = $this->decode_attachment_map( $row['attachments_map'] ?? [], $job_ids, (string) ( $row['workflow'] ?? '' ) );
        $attachment_ids = array_values(
            array_unique(
                array_merge(
                    $source_attachment_ids,
                    array_map( 'absint', array_values( $attachments_map ) )
                )
            )
        );
        foreach ( $attachment_ids as $attachment_id ) {
            $attachment_authorized = $this->authorize_image_attachment( $attachment_id );
            if ( is_wp_error( $attachment_authorized ) ) {
                return $attachment_authorized;
            }
        }

        return true;
    }

    /** @return bool|\WP_Error */
    private function authorize_image_attachment( int $attachment_id ) {
        if ( $attachment_id <= 0 || ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
            return $this->forbidden_response();
        }
        $attachment = get_post( $attachment_id );
        if ( ! ( $attachment instanceof \WP_Post ) || 'attachment' !== $attachment->post_type || ! wp_attachment_is_image( $attachment_id ) ) {
            return $this->unavailable_response();
        }
        return true;
    }

    private function forbidden_response(): \WP_Error {
        return Messages::error( 'forbidden', 403 );
    }

    private function unavailable_response(): \WP_Error {
        return new \WP_Error(
            'fotogenic_ai_active_run_unavailable',
            __( 'The requested generation run is unavailable.', 'fotogenic-ai' ),
            [ 'status' => 404 ]
        );
    }

    private function run_matches_request( array $row, int $user_id, string $workflow, string $target, int $subject_id, string $request_id ): bool {
        return ! empty( $row )
            && (int) ( $row['user_id'] ?? 0 ) === $user_id
            && $workflow === (string) ( $row['workflow'] ?? '' )
            && $target === (string) ( $row['run_target'] ?? '' )
            && $subject_id === (int) ( $row['subject_id'] ?? 0 )
            && hash_equals( $request_id, (string) ( $row['request_id'] ?? '' ) );
    }

    private function build_active_from_run( array $row ): array {
        $job_ids = $this->normalize_job_ids( $row['job_ids'] ?? [] );
		$workflow = sanitize_key( (string) ( $row['workflow'] ?? '' ) );
		$attachments_map = $this->decode_attachment_map( $row['attachments_map'] ?? [], $job_ids, $workflow );
        $source_attachment_ids = $this->decode_id_list( $row['source_attachment_ids'] ?? [] );
        return [
            'run_id'                 => (string) ( $row['run_id'] ?? '' ),
            'job_id'                 => (string) ( $job_ids[0] ?? '' ),
            'job_ids'                => $job_ids,
			'workflow'               => $workflow,
            'request_id'             => (string) ( $row['request_id'] ?? '' ),
            'user_id'                => (int) ( $row['user_id'] ?? 0 ),
            'run_target'             => (string) ( $row['run_target'] ?? '' ),
            'subject_id'             => (int) ( $row['subject_id'] ?? 0 ),
            'source_attachment_id'   => (int) ( $row['source_attachment_id'] ?? 0 ),
            'source_attachment_ids'  => $source_attachment_ids,
            'expected_output_count'  => max( 1, (int) ( $row['expected_output_count'] ?? count( $job_ids ) ) ),
            'estimated_cost'         => max( 0, (float) ( $row['estimated_cost'] ?? 0 ) ),
			'completed_job_ids'      => Generate_Output_Map::completed_job_ids_from_meta( $row['finalize_meta'] ?? [], $job_ids ),
			'attachment_ids'         => Generate_Output_Map::attachment_ids( $attachments_map ),
            'attachments_map'        => $attachments_map,
            'final_status'           => (string) ( $row['final_status'] ?? '' ),
            'started_at'             => $this->parse_time( $row['started_at'] ?? '' ),
            'ended_at'               => $this->parse_time( $row['ended_at'] ?? '' ),
            'error_code'             => (string) ( $row['error_code'] ?? '' ),
            'error_message'          => (string) ( $row['error_message'] ?? '' ),
            'uncertain'              => 'start_response_uncertain' === (string) ( $row['error_code'] ?? '' ) && empty( $job_ids ),
        ];
    }

    private function format_response( array $active, bool $is_active ): \WP_REST_Response {
        $job_ids = $this->normalize_job_ids( $active['job_ids'] ?? [] );
		$attachments_map = $this->decode_attachment_map( $active['attachments_map'] ?? [], $job_ids, (string) ( $active['workflow'] ?? '' ) );
        $attachments = [];
        foreach ( $job_ids as $job_id ) {
			foreach ( Generate_Output_Map::for_job( $attachments_map, $job_id ) as $attachment_id ) {
                $attachments[] = [
                    'jobId'        => $job_id,
                    'attachmentId' => $attachment_id,
                    'fullUrl'      => Media_Saver::get_attachment_view_url( $attachment_id, 'full' ),
                ];
            }
        }
        $data = [
            'hasActive'           => $is_active,
            'runId'               => (string) ( $active['run_id'] ?? '' ),
            'status'              => (string) ( $active['final_status'] ?? '' ),
            'jobIds'              => $job_ids,
            'attachmentsMap'      => $attachments_map,
            'attachments'         => $attachments,
            'workflow'            => (string) ( $active['workflow'] ?? '' ),
            'requestId'           => (string) ( $active['request_id'] ?? '' ),
            'sourceType'          => (string) ( $active['run_target'] ?? '' ),
            'subjectId'           => (int) ( $active['subject_id'] ?? 0 ),
            'startedAt'           => (int) ( $active['started_at'] ?? 0 ),
            'endedAt'             => (int) ( $active['ended_at'] ?? 0 ),
            'expectedOutputCount' => (int) ( $active['expected_output_count'] ?? count( $job_ids ) ),
            'estimatedCost'       => (float) ( $active['estimated_cost'] ?? 0 ),
            'errorCode'           => (string) ( $active['error_code'] ?? '' ),
            'errorMessage'        => (string) ( $active['error_message'] ?? '' ),
            'uncertain'           => ! empty( $active['uncertain'] ),
        ];
        $run_id  = (string) ( $active['run_id'] ?? '' );
        $user_id = (int) ( $active['user_id'] ?? 0 );
        if ( '' !== $run_id && $user_id > 0 ) {
            $projection = ( new Workflow_Response_Factory() )->present_owned_run( Logger::instance()->get_run_row( $run_id ), $user_id );
            if ( ! empty( $projection ) ) {
                $data = array_merge( $data, $projection );
            }
        }
        return rest_ensure_response( $data );
    }

    private function empty_response(): \WP_REST_Response {
        return rest_ensure_response( [ 'hasActive' => false ] );
    }

    private function decode_attachment_map( $value, array $job_ids, string $workflow ): array {
        if ( is_string( $value ) ) {
            $decoded = json_decode( $value, true );
            $value = is_array( $decoded ) ? $decoded : [];
        }
        if ( ! is_array( $value ) ) {
            return [];
        }
		return Generate_Output_Map::normalize( $value, $job_ids, sanitize_key( $workflow ) );
    }

    private function decode_id_list( $value ): array {
        if ( is_string( $value ) ) {
            $decoded = json_decode( $value, true );
            $value = is_array( $decoded ) ? $decoded : [];
        }
        return is_array( $value )
            ? array_values( array_unique( array_filter( array_map( 'absint', $value ) ) ) )
            : [];
    }

    private function parse_time( $value ): int {
        $value = trim( (string) $value );
        return '' !== $value ? (int) strtotime( $value . ' UTC' ) : 0;
    }

    private function is_active_status( string $status ): bool {
        return in_array( $status, [ 'starting', 'processing' ], true );
    }

    private function is_known_status( string $status ): bool {
        return in_array( $status, [ 'starting', 'processing', 'completed', 'partial', 'failed', 'timeout', 'stale' ], true );
    }

    private function normalize_job_ids( $job_ids ): array {
        if ( is_string( $job_ids ) ) {
            $trimmed = trim( $job_ids );
            if ( '' !== $trimmed && ( fotogenic_ai_str_starts_with( $trimmed, '[' ) || fotogenic_ai_str_starts_with( $trimmed, '{' ) ) ) {
                $decoded = json_decode( $trimmed, true );
                if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
                    $job_ids = $decoded;
                } else {
                    $job_ids = [ $job_ids ];
                }
            } else {
                $job_ids = [ $job_ids ];
            }
        }
        if ( ! is_array( $job_ids ) ) {
            return [];
        }
        $normalized = [];
        foreach ( $job_ids as $job_id ) {
            $job_id = trim( (string) $job_id );
            if ( '' !== $job_id ) {
                $normalized[] = $job_id;
            }
        }
        return array_values( array_unique( $normalized ) );
    }
}
