<?php

namespace HelpGent\App\MediaDrivers;

defined( 'ABSPATH' ) || exit;

use Exception;
use HelpGent\App\Contracts\MediaDriver;
use HelpGent\App\DTO\AttachmentDTO;
use HelpGent\App\DTO\AttachmentFileDTO;
use HelpGent\App\DTO\AttachmentUploadSessionDTO;
use HelpGent\App\DTO\ChunkFileDTO;
use HelpGent\App\DTO\ChunkFileStateDTO;
use HelpGent\App\Repositories\AttachmentUploadSessionRepository;

abstract class MediaDriverService implements MediaDriver {
    /**
     * @throws Exception Could not upload the file, please try again
     */
    public function upload( array $file ) : AttachmentFileDTO {
        helpgent_require_media_uploader_files();
        helpgent_prepare_upload_directory();

        add_filter( 'upload_dir', [ $this, 'change_upload_dir' ] );

        $file['name'] = helpgent_generate_unique_file_name( $file['name'] );

        $uploaded_file = wp_handle_upload( $file, [ 'test_form' => false ], time() );

        remove_filter( 'upload_dir', [ $this, 'change_upload_dir' ] );

        if ( ! empty( $uploaded_file['error'] ) ) {
            throw new Exception( esc_html( $uploaded_file['error'] ), 500 );
        }

        $file_name = basename( $uploaded_file['file'] );

        return ( new AttachmentFileDTO() )->set_file_name( $file_name )->set_file_size( $file['size'] )->set_mime_type( $uploaded_file['type'] );
    }

    /**
     * @throws Exception Could not create the file, please try again
     */
    public function create_file( string $file_name, string $file_contents ) : AttachmentFileDTO {
        helpgent_prepare_upload_directory();

        $dest_path = helpgent_upload_dir( $file_name );
        $file_size = file_put_contents( $dest_path, trim( $file_contents ) );

        if ( empty( $file_size ) ) {
            throw new Exception( esc_html__( 'Could not create the file, please try again', 'helpgent' ), 500 );
        }

        $mime_type = helpgent_get_file_mime_type( $dest_path );

        if ( empty( $mime_type ) ) {
            throw new Exception( esc_html__( 'The type is not supported', 'helpgent' ), 500 );
        }

        return ( new AttachmentFileDTO() )->set_file_name( $file_name )->set_file_size( $file_size )->set_mime_type( $mime_type );
    }

    public function change_upload_dir( array $uploads ) : array {
        $uploads['path']   = helpgent_upload_dir();
        $uploads['url']    = helpgent_upload_url();
        $uploads['subdir'] = '';

        return $uploads;
    }

    public function get( array $attachment ) : AttachmentFileDTO {
        $file_title = $attachment['title'];
        $file_size  = $attachment['size'];
        $mime_type  = $attachment['mime_type'];

        return ( new AttachmentFileDTO() )->set_file_name( $file_title )->set_file_size( $file_size )->set_mime_type( $mime_type );
    }

    public function create_upload_session( AttachmentDTO $file ) : AttachmentUploadSessionDTO {
        /**
         * @var AttachmentUploadSessionRepository $upload_session_repository
         */
        $upload_session_repository = helpgent_singleton( AttachmentUploadSessionRepository::class );

        $upload_session_dto = ( new AttachmentUploadSessionDTO() )
            ->set_title( $file->get_title() )
            ->set_file_size( $file->get_file_size() )
            ->set_mime_type( $file->get_mime_type() )
            ->set_storage( $file->get_storage() )
            ->set_upload_token( ! empty( $file->get_upload_token() ) ? $file->get_upload_token() : helpgent_generate_token() );

        $upload_id = $upload_session_repository->create( $upload_session_dto );
        $upload_session_dto->set_id( $upload_id );
        
        return $upload_session_dto;
    }

    /**
     * @throws Exception
     */
    public function upload_chunk( ChunkFileDTO $chunk_file, AttachmentUploadSessionDTO $upload_session ) : ChunkFileStateDTO {
        $this->add_chunk_content( $chunk_file, $upload_session );

        $last_sent_bytes = $chunk_file->get_range_to() - 1;

        if ( $chunk_file->get_range_to() === $chunk_file->get_file_size() ) {
            $file_size     = $this->get_chunk_file_size( $chunk_file->get_file_name() );
            $new_file_name = $this->rename_chunk_file( $chunk_file->get_file_name() );

            $file = ( new AttachmentFileDTO() )
                ->set_file_name( $new_file_name )->set_mime_type( $chunk_file->get_mime_type() )
                ->set_file_size( is_null( $file_size ) ? $chunk_file->get_file_size() : $file_size );

            return ( new ChunkFileStateDTO() )
                ->set_is_done( true )
                ->set_last_sent_bytes( $last_sent_bytes )
                ->set_file( $file );
        }

        return ( new ChunkFileStateDTO() )
            ->set_is_done( false )
            ->set_last_sent_bytes( $last_sent_bytes )
            ->set_file( null );
    }

    /**
     * @throws Exception
     */
    protected function add_chunk_content( ChunkFileDTO $chunk_file, AttachmentUploadSessionDTO $upload_session ) : void {
        $this->validate_chunk_file( $chunk_file, $upload_session );

        $draft_file_name = pathinfo( $chunk_file->get_file_name(), PATHINFO_FILENAME );
        $draft_file_path = helpgent_upload_dir( $draft_file_name );

        if ( ! file_exists( $draft_file_path ) ) {
            file_put_contents( $draft_file_path, '' );
        }

        file_put_contents( $draft_file_path, $chunk_file->get_content(), FILE_APPEND );
    }

    /**
     * @throws Exception
     */
    protected function get_chunk_file_size( string $file_title ) : ?int {
        $draft_file_path = helpgent_upload_dir( pathinfo( $file_title, PATHINFO_FILENAME ) );

        if ( ! file_exists( $draft_file_path ) ) {
            return null;
        }

        return filesize( $draft_file_path );
    }

    /**
     * @throws Exception
     */
    protected function rename_chunk_file( string $file_title ) : ?string {
        $draft_file_name = pathinfo( $file_title, PATHINFO_FILENAME );
        $draft_file_path = helpgent_upload_dir( $draft_file_name );

        if ( ! file_exists( $draft_file_path ) ) {
            throw new Exception( 'File not found.', 500 );
        }

        return helpgent_rename_attachment_file( $draft_file_name, $file_title );
    }

    protected function validate_chunk_file( ChunkFileDTO $chunk_file, AttachmentUploadSessionDTO $upload_session ) : void {
        $current_from_range = ( $upload_session->get_last_sent_bytes() === 0 ) ? 0 : $upload_session->get_last_sent_bytes() + 1;

        if ( $chunk_file->get_range_from() !== $current_from_range ) {
            // translators: %s range
            throw new Exception( sprintf( esc_html__( 'Invalid range specified. Current valid from range is %s', 'helpgent' ), esc_attr( $current_from_range ) ), 403 );
        }

        if ( $chunk_file->get_range_from() > $upload_session->get_file_size() ) {
            throw new Exception( esc_html__( 'Invalid range specified. Range from cant be greater then file size', 'helpgent' ), 403 );
        }

        if ( $chunk_file->get_range_to() < 0 ) {
            throw new Exception( esc_html__( 'Invalid range specified. Range to cant be less then 0', 'helpgent' ), 403 );
        }

        if ( $chunk_file->get_range_to() > $upload_session->get_file_size() ) {
            throw new Exception( esc_html__( 'Invalid range specified. Range to cant be greater then file size', 'helpgent' ), 403 );
        }

        $range_length = $chunk_file->get_range_to() - $chunk_file->get_range_from();

        if ( 0 === $range_length ) {
            throw new Exception( esc_html__( 'Invalid range specified. Content length must be grater then 0', 'helpgent' ), 403 );
        }

        if ( $range_length !== strlen( $chunk_file->get_content() ) ) {
            throw new Exception( esc_html__( 'The file content length does not match with the specified range', 'helpgent' ), 422 );
        }
    }

    public function get_file_url( ?string $file_name, ?string $file_id = null ): string {
        return helpgent_upload_url( $file_name );
    }
}
