package com.cappielloantonio.tempo.util;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import android.webkit.MimeTypeMap;

import androidx.core.app.NotificationCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.media3.common.MediaItem;

import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.Normalizer;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExternalAudioWriter {

    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
    private static final int BUFFER_SIZE = 8192;
    private static final int CONNECT_TIMEOUT_MS = 15_000;
    private static final int READ_TIMEOUT_MS = 60_000;

    private ExternalAudioWriter() {
    }

    private static String sanitizeFileName(String name) {
        String sanitized = name.replaceAll("[\\/:*?\\\"<>|]", "_");
        sanitized = sanitized.replaceAll("\\s+", " ").trim();
        return sanitized;
    }

    private static String normalizeForComparison(String name) {
        String s = sanitizeFileName(name);
        s = Normalizer.normalize(s, Normalizer.Form.NFKD);
        s = s.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
        return s.toLowerCase(Locale.ROOT);
    }

    private static DocumentFile findFile(DocumentFile dir, String fileName) {
        String normalized = normalizeForComparison(fileName);
        for (DocumentFile file : dir.listFiles()) {
            if (file.isDirectory()) continue;
            String existing = file.getName();
            if (existing != null && normalizeForComparison(existing).equals(normalized)) {
                return file;
            }
        }
        return null;
    }

    public static void downloadToUserDirectory(Context context, Child child) {
        if (context == null || child == null) {
            return;
        }
        Context appContext = context.getApplicationContext();
        MediaItem mediaItem = MappingUtil.mapDownload(child);
        String fallbackName = child.getTitle() != null ? child.getTitle() : child.getId();
        EXECUTOR.execute(() -> performDownload(appContext, mediaItem, fallbackName, child));
    }

    private static void performDownload(Context context, MediaItem mediaItem, String fallbackName, Child child) {
        String uriString = Preferences.getDownloadDirectoryUri();
        if (uriString == null) {
            notifyUnavailable(context);
            return;
        }

        DocumentFile directory = DocumentFile.fromTreeUri(context, Uri.parse(uriString));
        if (directory == null || !directory.canWrite()) {
            notifyFailure(context, "Cannot write to folder.");
            return;
        }

        String artist = child.getArtist() != null ? child.getArtist() : "";
        String title = child.getTitle() != null ? child.getTitle() : fallbackName;
        String album = child.getAlbum() != null ? child.getAlbum() : "";
        String baseName = artist.isEmpty() ? title : artist + " - " + title;
        if (!album.isEmpty()) baseName += " (" + album + ")";
        if (baseName.isEmpty()) {
            baseName = fallbackName != null ? fallbackName : "download";
        }
        String metadataKey = normalizeForComparison(baseName);

        Uri mediaUri = mediaItem != null && mediaItem.requestMetadata != null
                ? mediaItem.requestMetadata.mediaUri
                : null;
        if (mediaUri == null) {
            notifyFailure(context, "Invalid media URI.");
            ExternalDownloadMetadataStore.remove(metadataKey);
            return;
        }

        String scheme = mediaUri.getScheme() != null ? mediaUri.getScheme().toLowerCase(Locale.ROOT) : "";

        HttpURLConnection connection = null;
        DocumentFile sourceDocument = null;
        File sourceFile = null;
        long remoteLength = -1;
        String mimeType = null;
        DocumentFile targetFile = null;

        try {
            if (scheme.equals("http") || scheme.equals("https")) {
                connection = (HttpURLConnection) new URL(mediaUri.toString()).openConnection();
                connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
                connection.setReadTimeout(READ_TIMEOUT_MS);
                connection.setRequestProperty("Accept-Encoding", "identity");
                connection.connect();

                int responseCode = connection.getResponseCode();
                if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
                    notifyFailure(context, "Server returned " + responseCode);
                    ExternalDownloadMetadataStore.remove(metadataKey);
                    return;
                }

                mimeType = connection.getContentType();
                remoteLength = connection.getContentLengthLong();
            } else if (scheme.equals("content")) {
                sourceDocument = DocumentFile.fromSingleUri(context, mediaUri);
                mimeType = context.getContentResolver().getType(mediaUri);
                if (sourceDocument != null) {
                    remoteLength = sourceDocument.length();
                }
            } else if (scheme.equals("file")) {
                String path = mediaUri.getPath();
                if (path != null) {
                    sourceFile = new File(path);
                    if (sourceFile.exists()) {
                        remoteLength = sourceFile.length();
                    }
                }
                String ext = MimeTypeMap.getFileExtensionFromUrl(mediaUri.toString());
                if (ext != null && !ext.isEmpty()) {
                    mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
                }
            } else {
                notifyFailure(context, "Unsupported media URI.");
                ExternalDownloadMetadataStore.remove(metadataKey);
                return;
            }

            if (mimeType == null || mimeType.isEmpty()) {
                mimeType = "application/octet-stream";
            }

            String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
            if ((extension == null || extension.isEmpty()) && sourceDocument != null && sourceDocument.getName() != null) {
                String name = sourceDocument.getName();
                int dot = name.lastIndexOf('.');
                if (dot >= 0 && dot < name.length() - 1) {
                    extension = name.substring(dot + 1);
                }
            }
            if ((extension == null || extension.isEmpty()) && sourceFile != null) {
                String name = sourceFile.getName();
                int dot = name.lastIndexOf('.');
                if (dot >= 0 && dot < name.length() - 1) {
                    extension = name.substring(dot + 1);
                }
            }
            if (extension == null || extension.isEmpty()) {
                String suffix = child.getSuffix();
                if (suffix != null && !suffix.isEmpty()) {
                    extension = suffix;
                } else {
                    extension = "bin";
                }
            }

            String sanitized = sanitizeFileName(baseName);
            if (sanitized.isEmpty()) sanitized = sanitizeFileName(fallbackName);
            if (sanitized.isEmpty()) sanitized = "download";
            String fileName = sanitized + "." + extension;

            DocumentFile existingFile = findFile(directory, fileName);
            Long recordedSize = ExternalDownloadMetadataStore.getSize(metadataKey);
            if (existingFile != null && existingFile.exists()) {
                long localLength = existingFile.length();
                boolean matches = false;
                if (remoteLength > 0 && localLength == remoteLength) {
                    matches = true;
                } else if (remoteLength <= 0 && recordedSize != null && localLength == recordedSize) {
                    matches = true;
                }
                if (matches) {
                    ExternalDownloadMetadataStore.recordSize(metadataKey, localLength);
                    recordDownload(child, existingFile.getUri());
                    ExternalAudioReader.refreshCacheAsync();
                    notifyExists(context, fileName);
                    return;
                } else {
                    existingFile.delete();
                    ExternalDownloadMetadataStore.remove(metadataKey);
                }
            }

            targetFile = directory.createFile(mimeType, fileName);
            if (targetFile == null) {
                notifyFailure(context, "Failed to create file.");
                return;
            }

            Uri targetUri = targetFile.getUri();
            try (InputStream in = openInputStream(context, mediaUri, scheme, connection, sourceFile);
                 OutputStream out = context.getContentResolver().openOutputStream(targetUri)) {
                if (out == null) {
                    notifyFailure(context, "Cannot open output stream.");
                    targetFile.delete();
                    return;
                }

                byte[] buffer = new byte[BUFFER_SIZE];
                int len;
                long total = 0;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                    total += len;
                }
                out.flush();

                if (total <= 0) {
                    targetFile.delete();
                    ExternalDownloadMetadataStore.remove(metadataKey);
                    notifyFailure(context, "Empty download.");
                    return;
                }

                if (remoteLength > 0 && total != remoteLength) {
                    targetFile.delete();
                    ExternalDownloadMetadataStore.remove(metadataKey);
                    notifyFailure(context, "Incomplete download.");
                    return;
                }

                ExternalDownloadMetadataStore.recordSize(metadataKey, total);
                recordDownload(child, targetUri);
                notifySuccess(context, fileName, child, targetUri);
                ExternalAudioReader.refreshCacheAsync();
            }
        } catch (Exception e) {
            if (targetFile != null) {
                targetFile.delete();
            }
            ExternalDownloadMetadataStore.remove(metadataKey);
            notifyFailure(context, e.getMessage() != null ? e.getMessage() : "Download failed");
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private static void notifyUnavailable(Context context) {
        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                Uri.fromParts("package", context.getPackageName(), null));
        PendingIntent openSettings = PendingIntent.getActivity(context, 0, settingsIntent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
                .setContentTitle("No download folder set")
                .setContentText("Tap to set one in settings")
                .setSmallIcon(android.R.drawable.stat_notify_error)
                .setPriority(NotificationCompat.PRIORITY_LOW)
                .setSilent(true)
                .setContentIntent(openSettings)
                .setAutoCancel(true);

        manager.notify(1011, builder.build());
    }

    private static void notifyFailure(Context context, String message) {
        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
                .setContentTitle("Download failed")
                .setContentText(message)
                .setSmallIcon(android.R.drawable.stat_notify_error)
                .setAutoCancel(true);
        manager.notify((int) System.currentTimeMillis(), builder.build());
    }

    private static void notifySuccess(Context context, String name, Child child, Uri fileUri) {
        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
                .setContentTitle("Download complete")
                .setContentText(name)
                .setSmallIcon(android.R.drawable.stat_sys_download_done)
                .setAutoCancel(true);

        PendingIntent playIntent = buildPlayIntent(context, child, fileUri);
        if (playIntent != null) {
            builder.setContentIntent(playIntent);
        }

        manager.notify((int) System.currentTimeMillis(), builder.build());
    }

    private static void recordDownload(Child child, Uri fileUri) {
        if (child == null) {
            return;
        }

        Download download = new Download(child);
        download.setDownloadState(1);
        if (fileUri != null) {
            download.setDownloadUri(fileUri.toString());
        }

        new DownloadRepository().insert(download);
    }

    private static void notifyExists(Context context, String name) {
        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
                .setContentTitle("Already downloaded")
                .setContentText(name)
                .setSmallIcon(android.R.drawable.stat_sys_warning)
                .setAutoCancel(true);
        manager.notify((int) System.currentTimeMillis(), builder.build());
    }

    private static PendingIntent buildPlayIntent(Context context, Child child, Uri fileUri) {
        if (fileUri == null) return null;
        Intent intent = new Intent(context, MainActivity.class)
                .setAction(Constants.ACTION_PLAY_EXTERNAL_DOWNLOAD)
                .putExtra(Constants.EXTRA_DOWNLOAD_URI, fileUri.toString())
                .putExtra(Constants.EXTRA_DOWNLOAD_MEDIA_ID, child.getId())
                .putExtra(Constants.EXTRA_DOWNLOAD_TITLE, child.getTitle())
                .putExtra(Constants.EXTRA_DOWNLOAD_ARTIST, child.getArtist())
                .putExtra(Constants.EXTRA_DOWNLOAD_ALBUM, child.getAlbum())
                .putExtra(Constants.EXTRA_DOWNLOAD_DURATION, child.getDuration() != null ? child.getDuration() : 0)
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        int requestCode;
        if (child.getId() != null) {
            requestCode = Math.abs(child.getId().hashCode());
        } else {
            requestCode = Math.abs(fileUri.toString().hashCode());
        }

        return PendingIntent.getActivity(
                context,
                requestCode,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
    }

    private static InputStream openInputStream(Context context,
                                               Uri mediaUri,
                                               String scheme,
                                               HttpURLConnection connection,
                                               File sourceFile) throws IOException {
        switch (scheme) {
            case "http":
            case "https":
                if (connection == null) {
                    throw new IOException("Connection not initialized");
                }
                return connection.getInputStream();
            case "content":
                InputStream contentStream = context.getContentResolver().openInputStream(mediaUri);
                if (contentStream == null) {
                    throw new IOException("Cannot open content stream");
                }
                return contentStream;
            case "file":
                if (sourceFile == null || !sourceFile.exists()) {
                    throw new IOException("Missing source file");
                }
                return new FileInputStream(sourceFile);
            default:
                throw new IOException("Unsupported scheme " + scheme);
        }
    }
}
