<?php
namespace Domain\Tasks;

class TaskAttachmentRepository
{
  private $mdb;
  private $upload_dir;
  private $upload_url;
  private $table_ready = false;

  public function __construct( $mdb = null, $upload_dir = null, $upload_url = '/upload/task_attachments' )
  {
    if ( $mdb )
      $this -> mdb = $mdb;
    else
    {
      global $mdb;
      $this -> mdb = $mdb;
    }

    $this -> upload_dir = $upload_dir ? rtrim( $upload_dir, '/\\' ) : getcwd() . DIRECTORY_SEPARATOR . 'upload' . DIRECTORY_SEPARATOR . 'task_attachments';
    $this -> upload_url = rtrim( $upload_url, '/' );
  }

  public function listByTaskId( $task_id )
  {
    $this -> ensureStorage();
    $rows = $this -> mdb -> select( 'tasks_attachments', '*', [
      'AND' => [ 'task_id' => (int)$task_id, 'deleted' => 0 ],
      'ORDER' => [ 'id' => 'DESC' ]
    ] );

    if ( !is_array( $rows ) )
      return [];

    foreach ( $rows as &$row )
    {
      $row['title_effective'] = self::effectiveTitleWithExtension(
        isset( $row['title'] ) ? $row['title'] : null,
        isset( $row['original_name'] ) ? $row['original_name'] : '',
        isset( $row['file_ext'] ) ? $row['file_ext'] : '',
        isset( $row['stored_name'] ) ? $row['stored_name'] : ''
      );
      $row['url'] = $this -> buildPublicUrl( $row['relative_path'], $row['stored_name'] );
      $row['size_human'] = $this -> formatSize( (int)$row['file_size'] );
    }

    return $rows;
  }

  public function upload( $task_id, $user_id, array $file )
  {
    $this -> ensureStorage();

    if ( !isset( $file['error'] ) or $file['error'] !== UPLOAD_ERR_OK )
      return [ 'status' => 'error', 'msg' => 'Nie udalo sie wgrac pliku.' ];

    if ( !isset( $file['tmp_name'] ) or !is_uploaded_file( $file['tmp_name'] ) )
      return [ 'status' => 'error', 'msg' => 'Nieprawidlowy plik.' ];

    $original_name = trim( (string)$file['name'] );
    if ( $original_name === '' )
      return [ 'status' => 'error', 'msg' => 'Brak nazwy pliku.' ];

    $safe_original_name = self::sanitizeFileName( $original_name );
    $ext = strtolower( pathinfo( $safe_original_name, PATHINFO_EXTENSION ) );
    if ( $ext === '' && isset( $file['tmp_name'] ) && is_string( $file['tmp_name'] ) && file_exists( $file['tmp_name'] ) )
    {
      $detected_ext = self::detectExtensionFromContent( @file_get_contents( $file['tmp_name'] ) );
      if ( $detected_ext !== '' )
      {
        $ext = $detected_ext;
        $safe_original_name .= '.' . $ext;
      }
    }
    $stored_name = uniqid( 'att_', true ) . ( $ext ? '.' . $ext : '' );

    $relative_path = date( 'Y' ) . '/' . date( 'm' );
    $target_dir = $this -> upload_dir . DIRECTORY_SEPARATOR . str_replace( '/', DIRECTORY_SEPARATOR, $relative_path );
    if ( !is_dir( $target_dir ) )
      @mkdir( $target_dir, 0777, true );

    $target_file = $target_dir . DIRECTORY_SEPARATOR . $stored_name;
    if ( !move_uploaded_file( $file['tmp_name'], $target_file ) )
      return [ 'status' => 'error', 'msg' => 'Nie udalo sie zapisac pliku.' ];

    return $this -> storeMeta( $task_id, $user_id, $safe_original_name, $stored_name, $relative_path, $ext, isset( $file['size'] ) ? (int)$file['size'] : 0 );
  }

  public function uploadFromContent( $task_id, $user_id, $original_name, $content )
  {
    $this -> ensureStorage();

    $original_name = trim( (string)$original_name );
    if ( $original_name === '' )
      $original_name = 'zalacznik';

    $safe_original_name = self::sanitizeFileName( $original_name );
    $ext = strtolower( pathinfo( $safe_original_name, PATHINFO_EXTENSION ) );
    if ( $ext === '' )
    {
      $detected_ext = self::detectExtensionFromContent( (string)$content );
      if ( $detected_ext !== '' )
      {
        $ext = $detected_ext;
        $safe_original_name .= '.' . $ext;
      }
    }
    $stored_name = uniqid( 'att_', true ) . ( $ext ? '.' . $ext : '' );

    $relative_path = date( 'Y' ) . '/' . date( 'm' );
    $target_dir = $this -> upload_dir . DIRECTORY_SEPARATOR . str_replace( '/', DIRECTORY_SEPARATOR, $relative_path );
    if ( !is_dir( $target_dir ) )
      @mkdir( $target_dir, 0777, true );

    $target_file = $target_dir . DIRECTORY_SEPARATOR . $stored_name;
    if ( file_put_contents( $target_file, (string)$content ) === false )
      return [ 'status' => 'error', 'msg' => 'Nie udalo sie zapisac pliku.' ];

    return $this -> storeMeta( $task_id, $user_id, $safe_original_name, $stored_name, $relative_path, $ext, strlen( (string)$content ) );
  }

  public function rename( $attachment_id, $title )
  {
    $this -> ensureStorage();
    $title = trim( (string)$title );

    return $this -> mdb -> update( 'tasks_attachments', [
      'title' => $title !== '' ? $title : null
    ], [
      'AND' => [ 'id' => (int)$attachment_id, 'deleted' => 0 ]
    ] );
  }

  public function delete( $attachment_id, $deleted_by_user_id = null )
  {
    $this -> ensureStorage();

    $attachment = $this -> mdb -> get( 'tasks_attachments', '*', [
      'AND' => [ 'id' => (int)$attachment_id, 'deleted' => 0 ]
    ] );

    if ( !$attachment )
      return false;

    $file_path = $this -> upload_dir . DIRECTORY_SEPARATOR .
                 str_replace( '/', DIRECTORY_SEPARATOR, $attachment['relative_path'] ) .
                 DIRECTORY_SEPARATOR . $attachment['stored_name'];

    if ( file_exists( $file_path ) )
      @unlink( $file_path );

    return $this -> mdb -> update( 'tasks_attachments', [
      'deleted' => 1,
      'deleted_by' => $deleted_by_user_id ? (int)$deleted_by_user_id : null,
      'date_delete' => date( 'Y-m-d H:i:s' )
    ], [ 'id' => (int)$attachment_id ] );
  }

  public function purgeByTaskId( $task_id )
  {
    $this -> ensureStorage();

    $rows = $this -> mdb -> select( 'tasks_attachments', '*', [
      'task_id' => (int)$task_id
    ] );

    if ( !is_array( $rows ) )
      return false;

    foreach ( $rows as $attachment )
    {
      $file_path = $this -> resolveFilePath( $attachment );
      if ( $file_path !== '' and file_exists( $file_path ) )
        @unlink( $file_path );
    }

    $this -> mdb -> delete( 'tasks_attachments', [ 'task_id' => (int)$task_id ] );
    return true;
  }

  public static function effectiveTitle( $title, $fallback )
  {
    $title = trim( (string)$title );
    return $title !== '' ? $title : (string)$fallback;
  }

  public static function effectiveTitleWithExtension( $title, $fallback, $file_ext = '', $stored_name = '' )
  {
    $title = trim( (string)$title );
    $fallback = trim( (string)$fallback );

    if ( self::isGenericAttachmentTitle( $title ) && $fallback !== '' && !self::isGenericAttachmentTitle( $fallback ) )
      $effective = $fallback;
    else
      $effective = self::effectiveTitle( $title, $fallback );

    if ( pathinfo( $effective, PATHINFO_EXTENSION ) !== '' )
      return $effective;

    $file_ext = strtolower( trim( (string)$file_ext ) );
    if ( $file_ext === '' )
      $file_ext = strtolower( pathinfo( (string)$stored_name, PATHINFO_EXTENSION ) );

    if ( $file_ext !== '' )
      return $effective . '.' . $file_ext;

    return $effective;
  }

  private static function isGenericAttachmentTitle( $name )
  {
    $name = strtolower( trim( (string)$name ) );
    if ( $name === '' )
      return false;

    return (bool)preg_match( '/^zalacznik(?:\.[a-z0-9]{2,6})?$/i', $name );
  }

  public static function sanitizeFileName( $name )
  {
    $name = (string)$name;
    if ( $name !== '' && !preg_match( '//u', $name ) )
    {
      if ( function_exists( 'mb_convert_encoding' ) )
      {
        $converted = @mb_convert_encoding( $name, 'UTF-8', 'Windows-1250,ISO-8859-2,Windows-1252,ISO-8859-1' );
        if ( is_string( $converted ) && $converted !== '' )
          $name = $converted;
      }

      if ( !preg_match( '//u', $name ) && function_exists( 'iconv' ) )
      {
        $converted = @iconv( 'Windows-1250', 'UTF-8//IGNORE', $name );
        if ( is_string( $converted ) && $converted !== '' )
          $name = $converted;
      }
    }

    $name = preg_replace( '/[^\p{L}\p{N}\s\.\-_]+/u', '_', (string)$name );
    $name = preg_replace( '/\s+/', '_', $name );
    $name = trim( $name, '._-' );
    return $name !== '' ? $name : 'zalacznik';
  }

  public static function detectExtensionFromContent( $content )
  {
    if ( !is_string( $content ) || $content === '' )
      return '';

    $sample = substr( $content, 0, 4096 );

    if ( strpos( $sample, '%PDF-' ) !== false )
      return 'pdf';

    if ( substr( $sample, 0, 8 ) === "\x89PNG\r\n\x1A\n" )
      return 'png';

    if ( substr( $sample, 0, 3 ) === "\xFF\xD8\xFF" )
      return 'jpg';

    if ( substr( $sample, 0, 6 ) === 'GIF87a' || substr( $sample, 0, 6 ) === 'GIF89a' )
      return 'gif';

    if ( substr( $sample, 0, 2 ) === 'PK' )
      return 'zip';

    return '';
  }

  private function ensureStorage()
  {
    if ( !is_dir( $this -> upload_dir ) )
      @mkdir( $this -> upload_dir, 0777, true );

    if ( !$this -> table_ready )
    {
      $this -> ensureTable();
      $this -> table_ready = true;
    }
  }

  private function ensureTable()
  {
    $this -> mdb -> query(
      'CREATE TABLE IF NOT EXISTS `tasks_attachments` (
        `id` INT NOT NULL AUTO_INCREMENT,
        `task_id` INT NOT NULL,
        `user_id` INT NOT NULL,
        `title` VARCHAR(255) NULL,
        `original_name` VARCHAR(255) NOT NULL,
        `stored_name` VARCHAR(255) NOT NULL,
        `relative_path` VARCHAR(255) NOT NULL,
        `file_ext` VARCHAR(20) NULL,
        `file_size` INT UNSIGNED NOT NULL DEFAULT 0,
        `date_add` DATETIME NOT NULL,
        `deleted` TINYINT(1) NOT NULL DEFAULT 0,
        `deleted_by` INT NULL,
        `date_delete` DATETIME NULL,
        PRIMARY KEY (`id`),
        INDEX `idx_tasks_attachments_task` (`task_id`),
        INDEX `idx_tasks_attachments_deleted` (`deleted`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8'
    );
  }

  private function storeMeta( $task_id, $user_id, $safe_original_name, $stored_name, $relative_path, $ext, $size )
  {
    $insert = $this -> mdb -> insert( 'tasks_attachments', [
      'task_id' => (int)$task_id,
      'user_id' => (int)$user_id,
      'title' => null,
      'original_name' => $safe_original_name,
      'stored_name' => $stored_name,
      'relative_path' => $relative_path,
      'file_ext' => $ext,
      'file_size' => (int)$size,
      'date_add' => date( 'Y-m-d H:i:s' ),
      'deleted' => 0
    ] );

    if ( !$insert )
      return [ 'status' => 'error', 'msg' => 'Nie udalo sie zapisac zalacznika w bazie.' ];

    $attachment_id = $this -> mdb -> id();
    return [ 'status' => 'success', 'id' => (int)$attachment_id, 'relative_path' => $relative_path, 'stored_name' => $stored_name ];
  }

  private function buildPublicUrl( $relative_path, $stored_name )
  {
    return $this -> upload_url . '/' . trim( $relative_path, '/' ) . '/' . rawurlencode( $stored_name );
  }

  private function formatSize( $bytes )
  {
    if ( $bytes < 1024 )
      return $bytes . ' B';

    $kb = $bytes / 1024;
    if ( $kb < 1024 )
      return number_format( $kb, 1, '.', '' ) . ' KB';

    return number_format( $kb / 1024, 1, '.', '' ) . ' MB';
  }

  private function resolveFilePath( array $attachment )
  {
    if ( !isset( $attachment['relative_path'] ) or !isset( $attachment['stored_name'] ) )
      return '';

    return $this -> upload_dir . DIRECTORY_SEPARATOR .
           str_replace( '/', DIRECTORY_SEPARATOR, (string)$attachment['relative_path'] ) .
           DIRECTORY_SEPARATOR . (string)$attachment['stored_name'];
  }
}
