<?php

namespace DZ\ImageShield\Service;

use DZ\ImageShield\Shield;
use InvalidArgumentException;
use LogicException;
use XF;
use XF\FileWrapper;
use XF\Image\Gd;
use XF\Service\AbstractService;
use XF\Util\File;

/**
 * Service responsible for applying protective stamps to images.
 * Handles positioning, scaling, rotation and blending of stamps.
 */
class ShieldApplier extends AbstractService
{
    // Position type constants
    public const POS_TOP_LEFT = 'top_left';
    public const POS_TOP_RIGHT = 'top_right';
    public const POS_BOTTOM_LEFT = 'bottom_left';
    public const POS_BOTTOM_RIGHT = 'bottom_right';
    public const POS_CENTER = 'center';
    public const POS_TOP_LEFT_DIAGONAL = 'top_left_diagonal';
    public const POS_TOP_RIGHT_DIAGONAL = 'top_right_diagonal';
    public const POS_BOTTOM_LEFT_DIAGONAL = 'bottom_left_diagonal';
    public const POS_BOTTOM_RIGHT_DIAGONAL = 'bottom_right_diagonal';
    public const POS_CUSTOM = 'custom';

    // Stamp source types
    public const SOURCE_TEXT = 'text';
    public const SOURCE_IMAGE = 'image';

    // Legacy mode constants (for backward compatibility)
    public const MODE_TEXT = 'text';
    public const MODE_IMAGE = 'image';

    /** @var array<string, mixed> */
    protected array $configDefaults = [
        'fontSize' => 50,
        'textColor' => '#FFFFFF',
        'bgColor' => 'transparent',
        'fontFile' => '',
    ];

    protected FileWrapper $sourceFile;
    protected string $stampText = '';
    protected string $stampSource = self::SOURCE_TEXT;
    protected array $stampConfig = [];

    public function __construct(\XF\App $app, FileWrapper $file)
    {
        parent::__construct($app);
        $this->sourceFile = $file;
        $this->loadConfiguration();
    }

    /**
     * Set the stamp text content.
     */
    public function withText(string $text): self
    {
        $this->stampText = $text;
        return $this;
    }

    /**
     * Legacy alias for withText().
     */
    public function setOverlayText(string $text): self
    {
        return $this->withText($text);
    }

    /**
     * Set the stamp source type.
     */
    public function withSource(string $source): self
    {
        $allowed = [self::SOURCE_TEXT, self::SOURCE_IMAGE];
        if (!in_array($source, $allowed, true)) {
            throw new InvalidArgumentException('Invalid stamp source type.');
        }
        $this->stampSource = $source;
        return $this;
    }

    /**
     * Legacy alias for withSource().
     */
    public function setOverlayMode(string $mode): self
    {
        return $this->withSource($mode);
    }

    /**
     * Apply the stamp and return the processed file.
     */
    public function process(): FileWrapper
    {
        $source = $this->sourceFile;
        if ($source->getImageWidth() <= 0 || $source->getImageHeight() <= 0) {
            return $source;
        }

        $stampPath = $this->resolveStampAsset();
        if ($stampPath === null || !File::abstractedPathExists($stampPath)) {
            return $source;
        }

        $stampTemp = File::copyAbstractedPathToTempFile($stampPath);
        $stampInfo = @getimagesize($stampTemp);
        if (!is_array($stampInfo)) {
            XF::logError('[DZ ImageShield] Invalid stamp asset: ' . $stampPath);
            return $source;
        }

        /** @var Gd $stampImage */
        $stampImage = $this->app->imageManager()->imageFromFile($stampTemp);
        if (!$stampImage) {
            return $source;
        }

        // Apply opacity
        $this->applyOpacity($stampImage);

        // Apply scaling
        $this->applyScaling($stampImage, $source);

        // Apply rotation if diagonal position
        $this->applyRotation($stampImage);

        // Create base image copy
        $sourceTemp = File::getTempFile();
        File::copyFile($source->getFilePath(), $sourceTemp);
        $baseImage = $this->app->imageManager()->imageFromFile($sourceTemp);
        if ($baseImage === null) {
            throw new InvalidArgumentException('Failed to create image processor.');
        }

        // Calculate position and composite
        [$posX, $posY] = $this->calculatePosition(
            $baseImage->getWidth(),
            $baseImage->getHeight(),
            $stampImage
        );

        $baseImage->appendImageAt($posX, $posY, $stampImage->getImage());

        $outputPath = File::getTempFile();
        $baseImage->save($outputPath, null, 100);

        return new FileWrapper($outputPath, $source->getFileName());
    }

    /**
     * Alias for backward compatibility.
     */
    public function render(): FileWrapper
    {
        return $this->process();
    }

    /**
     * Load configuration from options.
     */
    protected function loadConfiguration(): void
    {
        $signature = $this->app->options()->dzImageShield_signature;
        if (!is_array($signature)) {
            $signature = [];
        }

        $type = $signature['type'] ?? self::SOURCE_TEXT;

        if ($type === self::SOURCE_IMAGE || $type === 'image') {
            $this->withSource(self::SOURCE_IMAGE);
            return;
        }

        $this->withSource(self::SOURCE_TEXT);
        $this->withText($signature['content'] ?? '');
        $this->stampConfig = $signature['config'] ?? [];
    }

    /**
     * Get or generate the stamp asset path.
     */
    protected function resolveStampAsset(): ?string
    {
        if ($this->stampSource === self::SOURCE_IMAGE) {
            return Shield::getOverlayImagePath();
        }

        if (strlen($this->stampText) === 0) {
            return null;
        }

        $config = array_replace($this->configDefaults, $this->stampConfig);
        $config['text'] = $this->stampText;

        // Set font path
        $fontPath = !empty($config['fontFile']) && file_exists($config['fontFile'])
            ? $config['fontFile']
            : OverlayRenderer::getDefaultFontPath();
        OverlayRenderer::setFontPath($fontPath);

        // Generate unique cache path
        $cacheKey = md5(serialize($config));
        $cachePath = 'internal-data://dz_imageshield/generated_' . $cacheKey . '.png';

        if (!File::abstractedPathExists($cachePath)) {
            $tempFile = OverlayRenderer::fromText(
                $this->stampText,
                (int) $config['fontSize'],
                $config['bgColor'],
                $config['textColor']
            );
            File::writeToAbstractedPath($cachePath, file_get_contents($tempFile));
        }

        return $cachePath;
    }

    /**
     * Apply opacity settings to stamp.
     */
    protected function applyOpacity(Gd $stamp): void
    {
        $opacity = (int) ($this->app->options()->dzImageShield_opacity ?? 80);

        if ($opacity < 0 || $opacity > 100) {
            $opacity = 80;
        }

        if ($opacity !== 100) {
            $stamp->setOpacity($opacity / 100);
        }
    }

    /**
     * Apply scaling to stamp based on source image dimensions.
     */
    protected function applyScaling(Gd $stamp, FileWrapper $source): void
    {
        $scaleX = (int) ($this->app->options()->dzImageShield_scaleX ?? 30);
        $scaleY = (int) ($this->app->options()->dzImageShield_scaleY ?? 25);

        $targetWidth = ($source->getImageWidth() / 100) * $scaleX;
        $targetHeight = ($source->getImageHeight() / 100) * $scaleY;

        $stamp->resize($targetWidth, $targetHeight, true);
    }

    /**
     * Apply rotation to stamp based on position settings.
     */
    protected function applyRotation(Gd $stamp): void
    {
        $position = $this->app->options()->dzImageShield_position;
        $posType = is_array($position) ? ($position['type'] ?? self::POS_BOTTOM_RIGHT) : self::POS_BOTTOM_RIGHT;

        // Only rotate for diagonal positions
        if (!str_contains($posType, '_diagonal')) {
            return;
        }

        // Determine rotation angle based on position
        $angle = match ($posType) {
            self::POS_TOP_LEFT_DIAGONAL, self::POS_BOTTOM_RIGHT_DIAGONAL => -45,
            self::POS_TOP_RIGHT_DIAGONAL, self::POS_BOTTOM_LEFT_DIAGONAL => 45,
            default => 0,
        };

        if ($angle === 0) {
            return;
        }

        $this->rotateStamp($stamp, $angle);
    }

    /**
     * Rotate the stamp image.
     */
    protected function rotateStamp(Gd $stamp, float $angle): void
    {
        $resource = $stamp->getImage();
        if (!$resource) {
            return;
        }

        imagealphablending($resource, true);
        imagesavealpha($resource, true);

        $transparent = imagecolorallocatealpha($resource, 0, 0, 0, 127);
        $rotated = imagerotate($resource, $angle * -1, $transparent);
        if (!$rotated) {
            return;
        }

        imagealphablending($rotated, true);
        imagesavealpha($rotated, true);

        // Use Closure to access protected setImage method
        $setter = \Closure::bind(function ($res): void {
            $this->setImage($res);
        }, $stamp, Gd::class);

        $setter($rotated);
        imagedestroy($resource);
    }

    /**
     * Calculate stamp position on source image.
     */
    protected function calculatePosition(int $imgWidth, int $imgHeight, Gd $stamp): array
    {
        $position = $this->app->options()->dzImageShield_position;
        $posType = is_array($position) ? ($position['type'] ?? self::POS_BOTTOM_RIGHT) : self::POS_BOTTOM_RIGHT;
        $customX = is_array($position) ? (float) ($position['x'] ?? 0) : 0;
        $customY = is_array($position) ? (float) ($position['y'] ?? 0) : 0;

        $stampW = $stamp->getWidth();
        $stampH = $stamp->getHeight();

        // Strip diagonal suffix for position calculation
        $basePos = str_replace('_diagonal', '', $posType);

        $x = 0;
        $y = 0;

        switch ($basePos) {
            case self::POS_TOP_LEFT:
                $x = 0;
                $y = 0;
                break;

            case self::POS_TOP_RIGHT:
                $x = $imgWidth - $stampW;
                $y = 0;
                break;

            case self::POS_BOTTOM_LEFT:
                $x = 0;
                $y = $imgHeight - $stampH;
                break;

            case self::POS_BOTTOM_RIGHT:
                $x = $imgWidth - $stampW;
                $y = $imgHeight - $stampH;
                break;

            case self::POS_CENTER:
                $x = (int) floor(($imgWidth - $stampW) / 2);
                $y = (int) floor(($imgHeight - $stampH) / 2);
                break;

            case self::POS_CUSTOM:
            default:
                $x = (int) floor(($imgWidth - $stampW) * ($customX / 100));
                $y = (int) floor(($imgHeight - $stampH) * ($customY / 100));
                break;
        }

        return [max($x, 0), max($y, 0)];
    }
}
