package com.qrcodestudio;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Base64;
import android.util.Log;

import com.getcapacitor.JSObject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.oned.Code128Writer;
import com.google.zxing.oned.Code39Writer;
import com.google.zxing.oned.Code93Writer;
import com.google.zxing.oned.CodaBarWriter;
import com.google.zxing.oned.EAN13Writer;
import com.google.zxing.oned.EAN8Writer;
import com.google.zxing.oned.ITFWriter;
import com.google.zxing.oned.UPCAWriter;
import com.google.zxing.oned.UPCEWriter;
import com.google.zxing.pdf417.PDF417Writer;
import com.google.zxing.datamatrix.DataMatrixWriter;
import com.google.zxing.aztec.AztecWriter;
import com.google.zxing.Writer;

import org.json.JSONException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

public class QRCodeGenerator {
    private static final String TAG = "QRCodeGenerator";
    private final Context context;
    
    public QRCodeGenerator(Context context) {
        this.context = context;
    }
    
    public JSObject generate(String type, JSObject data, JSObject options) throws Exception {
        // Format QR content based on type
        String content = formatQRData(type, data);
        
        // Get options
        int size = options.getInteger("size", 300);
        String foregroundColor = options.getString("foreground", "#000000");
        String backgroundColor = options.getString("background", "#FFFFFF");
        String errorCorrectionStr = options.getString("errorCorrection", "M");
        String format = options.getString("format", "png");
        double quality = options.getDouble("quality", 0.9);
        
        // Generate QR code
        Bitmap qrBitmap = generateQRCode(
            content, 
            size, 
            parseColor(foregroundColor), 
            parseColor(backgroundColor),
            parseErrorCorrection(errorCorrectionStr)
        );
        
        // Add logo if provided
        String logoData = options.getString("logo");
        if (logoData != null && !logoData.isEmpty()) {
            Bitmap logoBitmap = decodeBase64Bitmap(logoData);
            if (logoBitmap != null) {
                qrBitmap = addLogoToQR(qrBitmap, logoBitmap);
            }
        }
        
        // Encode to requested format
        String encodedImage = encodeBitmap(qrBitmap, format, quality);
        
        // Create result
        JSObject result = new JSObject();
        result.put("content", content);
        result.put("type", type);
        result.put("format", format);
        result.put("data", encodedImage);
        
        JSObject sizeObj = new JSObject();
        sizeObj.put("width", qrBitmap.getWidth());
        sizeObj.put("height", qrBitmap.getHeight());
        result.put("size", sizeObj);
        
        return result;
    }
    
    private String formatQRData(String type, JSObject data) throws JSONException {
        switch (type.toUpperCase()) {
            case "TEXT":
                return data.getString("text");
                
            case "WEBSITE":
                return data.getString("url");
                
            case "WIFI":
                return formatWifiData(data);
                
            case "EMAIL":
                return formatEmailData(data);
                
            case "PHONE":
                return "tel:" + data.getString("phoneNumber");
                
            case "SMS":
                return formatSmsData(data);
                
            case "WHATSAPP":
                return formatWhatsAppData(data);
                
            case "VCARD":
                return formatVCardData(data);
                
            case "MECARD":
                return formatMeCardData(data);
                
            case "LOCATION":
                return formatLocationData(data);
                
            case "EVENT":
                return formatEventData(data);
                
            case "CRYPTO":
                return formatCryptoData(data);
                
            case "SOCIAL_MEDIA":
                return formatSocialMediaData(data);
                
            case "PDF":
            case "VIDEO":
            case "IMAGES":
            case "MP3":
                return data.getString("url");
                
            case "FACEBOOK":
                return "https://www.facebook.com/" + data.getString("username");
                
            case "INSTAGRAM":
                return "https://www.instagram.com/" + data.getString("username");
                
            case "APPS":
                return formatAppData(data);
                
            case "LINKS_LIST":
                return formatLinksData(data);
                
            case "COUPON":
                return formatCouponData(data);
                
            case "MENU":
                return formatMenuData(data);
                
            case "BUSINESS":
                return formatBusinessData(data);
                
            default:
                // For custom types, return JSON string
                return data.toString();
        }
    }
    
    private String formatWifiData(JSObject data) throws JSONException {
        StringBuilder wifi = new StringBuilder("WIFI:");
        wifi.append("T:").append(data.getString("security", "WPA"));
        wifi.append(";S:").append(data.getString("ssid"));
        wifi.append(";P:").append(data.getString("password", ""));
        wifi.append(";H:").append(data.getBoolean("hidden", false));
        wifi.append(";;");
        return wifi.toString();
    }
    
    private String formatEmailData(JSObject data) throws JSONException {
        StringBuilder email = new StringBuilder("mailto:");
        email.append(data.getString("to"));
        
        String subject = data.getString("subject", "");
        String body = data.getString("body", "");
        
        if (!subject.isEmpty() || !body.isEmpty()) {
            email.append("?");
            boolean first = true;
            
            if (!subject.isEmpty()) {
                email.append("subject=").append(URLEncoder.encode(subject));
                first = false;
            }
            
            if (!body.isEmpty()) {
                if (!first) email.append("&");
                email.append("body=").append(URLEncoder.encode(body));
            }
        }
        
        return email.toString();
    }
    
    private String formatSmsData(JSObject data) throws JSONException {
        StringBuilder sms = new StringBuilder("sms:");
        sms.append(data.getString("phoneNumber"));
        
        String message = data.getString("message", "");
        if (!message.isEmpty()) {
            sms.append("?body=").append(URLEncoder.encode(message));
        }
        
        return sms.toString();
    }
    
    private String formatWhatsAppData(JSObject data) throws JSONException {
        StringBuilder whatsapp = new StringBuilder("whatsapp://send?");
        whatsapp.append("phone=").append(data.getString("phoneNumber"));
        
        String message = data.getString("message", "");
        if (!message.isEmpty()) {
            whatsapp.append("&text=").append(URLEncoder.encode(message));
        }
        
        return whatsapp.toString();
    }
    
    private String formatVCardData(JSObject data) throws JSONException {
        StringBuilder vcard = new StringBuilder();
        vcard.append("BEGIN:VCARD\n");
        vcard.append("VERSION:3.0\n");
        
        String firstName = data.getString("firstName", "");
        String lastName = data.getString("lastName", "");
        if (!firstName.isEmpty() || !lastName.isEmpty()) {
            vcard.append("FN:").append(firstName).append(" ").append(lastName).append("\n");
            vcard.append("N:").append(lastName).append(";").append(firstName).append(";;;\n");
        }
        
        String org = data.getString("organization", "");
        if (!org.isEmpty()) {
            vcard.append("ORG:").append(org).append("\n");
        }
        
        String phone = data.getString("phone", "");
        if (!phone.isEmpty()) {
            vcard.append("TEL:").append(phone).append("\n");
        }
        
        String email = data.getString("email", "");
        if (!email.isEmpty()) {
            vcard.append("EMAIL:").append(email).append("\n");
        }
        
        String website = data.getString("website", "");
        if (!website.isEmpty()) {
            vcard.append("URL:").append(website).append("\n");
        }
        
        vcard.append("END:VCARD");
        return vcard.toString();
    }
    
    private String formatMeCardData(JSObject data) throws JSONException {
        StringBuilder mecard = new StringBuilder("MECARD:");
        
        String firstName = data.getString("firstName", "");
        String lastName = data.getString("lastName", "");
        if (!firstName.isEmpty() || !lastName.isEmpty()) {
            mecard.append("N:").append(lastName).append(",").append(firstName).append(";");
        }
        
        String phone = data.getString("phone", "");
        if (!phone.isEmpty()) {
            mecard.append("TEL:").append(phone).append(";");
        }
        
        String email = data.getString("email", "");
        if (!email.isEmpty()) {
            mecard.append("EMAIL:").append(email).append(";");
        }
        
        mecard.append(";");
        return mecard.toString();
    }
    
    private String formatLocationData(JSObject data) throws JSONException {
        return String.format("geo:%f,%f", 
            data.getDouble("latitude"), 
            data.getDouble("longitude")
        );
    }
    
    private String formatEventData(JSObject data) throws JSONException {
        StringBuilder event = new StringBuilder();
        event.append("BEGIN:VEVENT\n");
        event.append("SUMMARY:").append(data.getString("title")).append("\n");
        event.append("DTSTART:").append(data.getString("start")).append("\n");
        event.append("DTEND:").append(data.getString("end")).append("\n");
        
        String location = data.getString("location", "");
        if (!location.isEmpty()) {
            event.append("LOCATION:").append(location).append("\n");
        }
        
        String description = data.getString("description", "");
        if (!description.isEmpty()) {
            event.append("DESCRIPTION:").append(description).append("\n");
        }
        
        event.append("END:VEVENT");
        return event.toString();
    }
    
    private String formatCryptoData(JSObject data) throws JSONException {
        StringBuilder crypto = new StringBuilder();
        crypto.append(data.getString("currency", "bitcoin"));
        crypto.append(":");
        crypto.append(data.getString("address"));
        
        if (data.has("amount")) {
            crypto.append("?amount=").append(data.getDouble("amount"));
        }
        
        return crypto.toString();
    }
    
    private String formatSocialMediaData(JSObject data) throws JSONException {
        String platform = data.getString("platform");
        String username = data.getString("username");
        
        switch (platform.toLowerCase()) {
            case "twitter":
                return "https://twitter.com/" + username;
            case "linkedin":
                return "https://linkedin.com/in/" + username;
            case "youtube":
                return "https://youtube.com/@" + username;
            case "tiktok":
                return "https://tiktok.com/@" + username;
            default:
                return data.getString("url", "");
        }
    }
    
    private String formatAppData(JSObject data) throws JSONException {
        String platform = data.getString("platform", "both");
        String appId = data.getString("appId");
        
        if ("android".equals(platform)) {
            return "market://details?id=" + appId;
        } else if ("ios".equals(platform)) {
            return "https://apps.apple.com/app/id" + appId;
        } else {
            // Return JSON with both links
            return data.toString();
        }
    }
    
    private String formatLinksData(JSObject data) throws JSONException {
        return data.toString();
    }
    
    private String formatCouponData(JSObject data) throws JSONException {
        return data.toString();
    }
    
    private String formatMenuData(JSObject data) throws JSONException {
        return data.toString();
    }
    
    private String formatBusinessData(JSObject data) throws JSONException {
        return data.toString();
    }
    
    private Bitmap generateQRCode(String content, int size, int foreground, int background, ErrorCorrectionLevel errorCorrection) throws WriterException {
        QRCodeWriter writer = new QRCodeWriter();
        
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.ERROR_CORRECTION, errorCorrection);
        hints.put(EncodeHintType.MARGIN, 1);
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        
        BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, size, size, hints);
        
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                bitmap.setPixel(x, y, bitMatrix.get(x, y) ? foreground : background);
            }
        }
        
        return bitmap;
    }
    
    private Bitmap addLogoToQR(Bitmap qrBitmap, Bitmap logoBitmap) {
        int qrWidth = qrBitmap.getWidth();
        int qrHeight = qrBitmap.getHeight();
        
        // Logo should be max 25% of QR code size
        int logoSize = Math.min(qrWidth, qrHeight) / 4;
        
        // Scale logo
        Bitmap scaledLogo = Bitmap.createScaledBitmap(logoBitmap, logoSize, logoSize, true);
        
        // Create combined bitmap
        Bitmap combined = Bitmap.createBitmap(qrWidth, qrHeight, qrBitmap.getConfig());
        Canvas canvas = new Canvas(combined);
        
        // Draw QR code
        canvas.drawBitmap(qrBitmap, 0, 0, null);
        
        // Draw white background for logo
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);
        
        int padding = logoSize / 10;
        int logoBackgroundSize = logoSize + (padding * 2);
        int logoX = (qrWidth - logoBackgroundSize) / 2;
        int logoY = (qrHeight - logoBackgroundSize) / 2;
        
        RectF backgroundRect = new RectF(logoX, logoY, logoX + logoBackgroundSize, logoY + logoBackgroundSize);
        canvas.drawRoundRect(backgroundRect, 10, 10, paint);
        
        // Draw logo
        canvas.drawBitmap(scaledLogo, logoX + padding, logoY + padding, null);
        
        return combined;
    }
    
    private String encodeBitmap(Bitmap bitmap, String format, double quality) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        Bitmap.CompressFormat compressFormat;
        switch (format.toLowerCase()) {
            case "png":
                compressFormat = Bitmap.CompressFormat.PNG;
                break;
            case "jpeg":
            case "jpg":
                compressFormat = Bitmap.CompressFormat.JPEG;
                break;
            case "webp":
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
                    compressFormat = Bitmap.CompressFormat.WEBP_LOSSLESS;
                } else {
                    compressFormat = Bitmap.CompressFormat.WEBP;
                }
                break;
            default:
                compressFormat = Bitmap.CompressFormat.PNG;
        }
        
        bitmap.compress(compressFormat, (int)(quality * 100), baos);
        byte[] imageBytes = baos.toByteArray();
        
        return Base64.encodeToString(imageBytes, Base64.NO_WRAP);
    }
    
    private Bitmap decodeBase64Bitmap(String base64String) {
        try {
            // Remove data URL prefix if present
            String cleanBase64 = base64String
                .replaceAll("data:image/[^;]+;base64,", "");
            
            byte[] decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT);
            return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
        } catch (Exception e) {
            Log.e(TAG, "Failed to decode base64 bitmap", e);
            return null;
        }
    }
    
    private int parseColor(String colorString) {
        try {
            return Color.parseColor(colorString);
        } catch (IllegalArgumentException e) {
            // Default to black if parsing fails
            return Color.BLACK;
        }
    }
    
    private ErrorCorrectionLevel parseErrorCorrection(String level) {
        switch (level.toUpperCase()) {
            case "L":
                return ErrorCorrectionLevel.L;
            case "M":
                return ErrorCorrectionLevel.M;
            case "Q":
                return ErrorCorrectionLevel.Q;
            case "H":
                return ErrorCorrectionLevel.H;
            default:
                return ErrorCorrectionLevel.M;
        }
    }
    
    public JSObject generateBarcode(String format, String data, int width, int height, boolean displayText, String outputFormat) throws Exception {
        // Get appropriate writer for format
        Writer writer = getBarcodeWriter(format);
        BarcodeFormat barcodeFormat = getBarcodeFormat(format);
        
        if (writer == null || barcodeFormat == null) {
            throw new Exception("Unsupported barcode format: " + format);
        }
        
        // Set encoding hints
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.MARGIN, 1);
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        
        // Generate barcode
        BitMatrix bitMatrix = writer.encode(data, barcodeFormat, width, height, hints);
        
        // Convert to bitmap
        Bitmap barcodeBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                barcodeBitmap.setPixel(x, y, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE);
            }
        }
        
        // Add text if requested
        if (displayText && !format.equals("QR_CODE") && !format.equals("DATA_MATRIX") && !format.equals("AZTEC") && !format.equals("PDF_417")) {
            barcodeBitmap = addTextToBarcode(barcodeBitmap, data);
        }
        
        // Encode to requested format
        String encodedImage = encodeBitmap(barcodeBitmap, outputFormat, 1.0);
        
        // Create result
        JSObject result = new JSObject();
        result.put("format", format);
        result.put("data", data);
        result.put("dataUrl", "data:image/" + outputFormat + ";base64," + encodedImage);
        result.put("width", barcodeBitmap.getWidth());
        result.put("height", barcodeBitmap.getHeight());
        
        return result;
    }
    
    private Writer getBarcodeWriter(String format) {
        switch (format) {
            case "CODE_128":
                return new Code128Writer();
            case "CODE_39":
                return new Code39Writer();
            case "CODE_93":
                return new Code93Writer();
            case "CODABAR":
                return new CodaBarWriter();
            case "EAN_13":
                return new EAN13Writer();
            case "EAN_8":
                return new EAN8Writer();
            case "ITF":
            case "ITF_14":
                return new ITFWriter();
            case "UPC_A":
                return new UPCAWriter();
            case "UPC_E":
                return new UPCEWriter();
            case "PDF_417":
                return new PDF417Writer();
            case "DATA_MATRIX":
                return new DataMatrixWriter();
            case "AZTEC":
                return new AztecWriter();
            case "QR_CODE":
                return new QRCodeWriter();
            default:
                return null;
        }
    }
    
    private BarcodeFormat getBarcodeFormat(String format) {
        switch (format) {
            case "CODE_128":
                return BarcodeFormat.CODE_128;
            case "CODE_39":
                return BarcodeFormat.CODE_39;
            case "CODE_93":
                return BarcodeFormat.CODE_93;
            case "CODABAR":
                return BarcodeFormat.CODABAR;
            case "EAN_13":
                return BarcodeFormat.EAN_13;
            case "EAN_8":
                return BarcodeFormat.EAN_8;
            case "ITF":
            case "ITF_14":
                return BarcodeFormat.ITF;
            case "UPC_A":
                return BarcodeFormat.UPC_A;
            case "UPC_E":
                return BarcodeFormat.UPC_E;
            case "PDF_417":
                return BarcodeFormat.PDF_417;
            case "DATA_MATRIX":
                return BarcodeFormat.DATA_MATRIX;
            case "AZTEC":
                return BarcodeFormat.AZTEC;
            case "QR_CODE":
                return BarcodeFormat.QR_CODE;
            default:
                return null;
        }
    }
    
    private Bitmap addTextToBarcode(Bitmap barcode, String text) {
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setTextSize(14);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setAntiAlias(true);
        
        // Calculate text bounds
        Rect bounds = new Rect();
        paint.getTextBounds(text, 0, text.length(), bounds);
        
        int textHeight = bounds.height() + 10; // Add padding
        int totalHeight = barcode.getHeight() + textHeight;
        
        // Create new bitmap with space for text
        Bitmap result = Bitmap.createBitmap(barcode.getWidth(), totalHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        
        // Fill background
        canvas.drawColor(Color.WHITE);
        
        // Draw barcode
        canvas.drawBitmap(barcode, 0, 0, null);
        
        // Draw text
        canvas.drawText(text, barcode.getWidth() / 2f, barcode.getHeight() + textHeight - 5, paint);
        
        return result;
    }
}