package ba.unsa.etf.si.bbqms.admin_service.implementation;

import ba.unsa.etf.si.bbqms.admin_service.api.AdvertisementService;
import ba.unsa.etf.si.bbqms.domain.Advertisement;
import ba.unsa.etf.si.bbqms.domain.AdvertisementMediaType;
import ba.unsa.etf.si.bbqms.domain.Tenant;
import ba.unsa.etf.si.bbqms.repository.AdvertisementRepository;
import ba.unsa.etf.si.bbqms.tenant_service.api.TenantService;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;

@Service
public class DefaultAdvertisementService implements AdvertisementService {
    private static final int DEFAULT_DURATION_SECONDS = 10;
    private static final Set<String> IMAGE_TYPES = Set.of(
            "image/jpeg",
            "image/png",
            "image/webp",
            "image/gif"
    );
    private static final Set<String> VIDEO_TYPES = Set.of(
            "video/mp4",
            "video/webm"
    );

    private final AdvertisementRepository advertisementRepository;
    private final TenantService tenantService;
    private final Path uploadRoot;

    public DefaultAdvertisementService(final AdvertisementRepository advertisementRepository,
                                       final TenantService tenantService,
                                       @Value("${ads.upload-dir:uploads/ads}") final String uploadDir) {
        this.advertisementRepository = advertisementRepository;
        this.tenantService = tenantService;
        this.uploadRoot = Path.of(uploadDir).toAbsolutePath().normalize();
    }

    @Override
    public Advertisement create(final String tenantCode,
                                final MultipartFile file,
                                final String title,
                                final Integer durationSeconds,
                                final Integer sortOrder,
                                final Boolean active) throws Exception {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("File is required.");
        }

        final String contentType = normalizeContentType(file.getContentType());
        final AdvertisementMediaType mediaType = resolveMediaType(contentType);
        final Tenant tenant = this.tenantService.findByCode(tenantCode);

        final String originalName = file.getOriginalFilename() != null ? file.getOriginalFilename() : "upload";
        final String safeFileName = sanitizeFileName(originalName);
        final String storedName = UUID.randomUUID() + "-" + safeFileName;
        final Path tenantDir = this.uploadRoot.resolve(tenantCode).normalize();
        Files.createDirectories(tenantDir);

        final Path destination = tenantDir.resolve(storedName).normalize();
        if (!destination.startsWith(tenantDir)) {
            throw new IllegalArgumentException("Invalid file path.");
        }

        try {
            Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
        } catch (final IOException exception) {
            throw new IllegalStateException("Failed to store advertisement file.", exception);
        }

        final String relativePath = this.uploadRoot.relativize(destination).toString().replace('\\', '/');
        final int nextSortOrder = sortOrder != null
                ? sortOrder
                : this.advertisementRepository.findByTenant_CodeOrderBySortOrderAscIdAsc(tenantCode).stream()
                .mapToInt(Advertisement::getSortOrder)
                .max()
                .orElse(-1) + 1;

        final Advertisement advertisement = new Advertisement(
                tenant,
                title,
                mediaType,
                originalName,
                contentType,
                relativePath,
                nextSortOrder,
                active == null || active,
                durationSeconds != null ? durationSeconds : DEFAULT_DURATION_SECONDS,
                Instant.now()
        );

        return this.advertisementRepository.save(advertisement);
    }

    @Override
    public List<Advertisement> listByTenant(final String tenantCode) {
        this.tenantService.findByCode(tenantCode);
        return this.advertisementRepository.findByTenant_CodeOrderBySortOrderAscIdAsc(tenantCode);
    }

    @Override
    public List<Advertisement> listActiveByTenant(final String tenantCode) {
        this.tenantService.findByCode(tenantCode);
        return this.advertisementRepository.findByTenant_CodeAndActiveTrueOrderBySortOrderAscIdAsc(tenantCode);
    }

    @Override
    public Advertisement update(final String tenantCode,
                                final long adId,
                                final String title,
                                final Integer durationSeconds,
                                final Integer sortOrder,
                                final Boolean active) {
        final Advertisement advertisement = getForTenant(tenantCode, adId);

        if (title != null) {
            advertisement.setTitle(title);
        }
        if (durationSeconds != null) {
            advertisement.setDurationSeconds(durationSeconds);
        }
        if (sortOrder != null) {
            advertisement.setSortOrder(sortOrder);
        }
        if (active != null) {
            advertisement.setActive(active);
        }

        return this.advertisementRepository.save(advertisement);
    }

    @Override
    public void delete(final String tenantCode, final long adId) throws Exception {
        final Advertisement advertisement = getForTenant(tenantCode, adId);
        final Path filePath = resolveStoredPath(advertisement.getStoragePath());

        this.advertisementRepository.delete(advertisement);

        try {
            Files.deleteIfExists(filePath);
        } catch (final IOException exception) {
            // Row is already removed; log-worthy but don't fail the API for orphan files.
        }
    }

    @Override
    public Advertisement getForTenant(final String tenantCode, final long adId) {
        final Advertisement advertisement = this.advertisementRepository.get(adId);
        if (!advertisement.getTenant().getCode().equals(tenantCode)) {
            throw new EntityNotFoundException("Advertisement not found for tenant: " + tenantCode);
        }
        return advertisement;
    }

    @Override
    public Advertisement findById(final long adId) {
        return this.advertisementRepository.get(adId);
    }

    @Override
    public Resource loadMedia(final long adId) throws Exception {
        final Advertisement advertisement = this.advertisementRepository.get(adId);
        final Path filePath = resolveStoredPath(advertisement.getStoragePath());
        final Resource resource = new UrlResource(filePath.toUri());
        if (!resource.exists() || !resource.isReadable()) {
            throw new EntityNotFoundException("Advertisement media file not found.");
        }
        return resource;
    }

    private Path resolveStoredPath(final String storagePath) {
        final Path resolved = this.uploadRoot.resolve(storagePath).normalize();
        if (!resolved.startsWith(this.uploadRoot)) {
            throw new IllegalArgumentException("Invalid storage path.");
        }
        return resolved;
    }

    private static String normalizeContentType(final String contentType) {
        if (contentType == null || contentType.isBlank()) {
            throw new IllegalArgumentException("Missing content type.");
        }
        return contentType.toLowerCase(Locale.ROOT).split(";")[0].trim();
    }

    private static AdvertisementMediaType resolveMediaType(final String contentType) {
        if (IMAGE_TYPES.contains(contentType)) {
            return AdvertisementMediaType.IMAGE;
        }
        if (VIDEO_TYPES.contains(contentType)) {
            return AdvertisementMediaType.VIDEO;
        }
        throw new IllegalArgumentException("Unsupported media type: " + contentType);
    }

    private static String sanitizeFileName(final String originalName) {
        final String name = Path.of(originalName).getFileName().toString();
        final String sanitized = name.replaceAll("[^a-zA-Z0-9._-]", "_");
        return sanitized.isBlank() ? "upload" : sanitized;
    }
}
