import os
import json
import uuid
import tempfile
from urllib.parse import urlparse
from flask import Flask, jsonify, request, send_from_directory
from flask_cors import CORS
from werkzeug.utils import secure_filename
import requests
import firebase_admin
from firebase_admin import credentials, firestore, storage
import threading
import logging

app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}})

# -------------------------------
# Configuration and Initialization
# -------------------------------

# Project directory
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# Data directories and file paths
DATA_DIR = os.path.join(BASE_DIR, 'data')
OUTPUT_JSON = os.path.join(DATA_DIR, 'output2.json')  # 'data' folder
OUTPUT_APPROVED_JSON = os.path.join(DATA_DIR, 'outputapproved.json')
APPROVED_IMAGES_DIR = os.path.join(BASE_DIR, 'approvedimages')

# Create directories if they don't exist
os.makedirs(APPROVED_IMAGES_DIR, exist_ok=True)
os.makedirs(DATA_DIR, exist_ok=True)

# Initialize JSON files if they don't exist
for file_path in [OUTPUT_JSON, OUTPUT_APPROVED_JSON]:
    if not os.path.exists(file_path):
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump({}, f, ensure_ascii=False, indent=4)

# Firebase Admin SDK Initialization
FIREBASE_CREDENTIALS = os.getenv('FIREBASE_CREDENTIALS', 'cre.json')  # Path to your Firebase service account key
FIREBASE_STORAGE_BUCKET = os.getenv('FIREBASE_STORAGE_BUCKET', 'readarabic-dec46.appspot.com')  # Replace with your Firebase Storage Bucket name

if not os.path.exists(FIREBASE_CREDENTIALS):
    app.logger.critical(f"Firebase service account key not found: {FIREBASE_CREDENTIALS}")
    exit(1)

cred = credentials.Certificate(FIREBASE_CREDENTIALS)
firebase_admin.initialize_app(cred, {
    'storageBucket': FIREBASE_STORAGE_BUCKET
})

db = firestore.client()
bucket = storage.bucket()

# Global threading lock for file operations
file_lock = threading.Lock()

# -------------------------------
# Logging Configuration
# -------------------------------

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# -------------------------------
# Utility Functions
# -------------------------------

def atomic_write(file_path, data, encoding='utf-8'):
    """
    Atomically write JSON data to a file.
    """
    try:
        with tempfile.NamedTemporaryFile('w', delete=False, encoding=encoding, dir=os.path.dirname(file_path)) as tmp_file:
            json.dump(data, tmp_file, ensure_ascii=False, indent=4)
            temp_name = tmp_file.name
        os.replace(temp_name, file_path)
        logger.info(f"Atomically wrote to {file_path}")
    except Exception as e:
        app.logger.error(f"Atomic write failed for {file_path}: {e}")
        raise

ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'}
MAX_IMAGE_SIZE = 5 * 1024 * 1024  # 5 MB

def is_allowed_file(filepath):
    """
    Check if the file has an allowed extension.
    """
    _, ext = os.path.splitext(filepath)
    return ext.lower() in ALLOWED_EXTENSIONS

def is_url(path):
    """
    Check if the given path is a valid URL.
    """
    try:
        result = urlparse(path)
        return all([result.scheme, result.netloc])
    except ValueError:
        return False

def download_image(image_url, save_directory):
    """
    Downloads an image from the given URL and saves it to the specified directory.
    Returns the local file path if successful, else None.
    """
    try:
        logger.info(f"Attempting to download image from URL: {image_url}")
        headers = {
            'User-Agent': 'Mozilla/5.0'  # Some servers require a user-agent header
        }
        response = requests.get(image_url, timeout=30, headers=headers)
        response.raise_for_status()

        content_type = response.headers.get('Content-Type', '')
        if 'image' not in content_type:
            logger.error(f"URL does not point to an image. Content-Type: {content_type}")
            return None

        # Determine file extension from Content-Type
        ext_mapping = {
            'image/jpeg': '.jpg',
            'image/png': '.png',
            'image/gif': '.gif',
            'image/bmp': '.bmp'
        }
        ext = ext_mapping.get(content_type.lower(), '.jpg')  # Default to .jpg

        # Generate a unique filename
        unique_id = uuid.uuid4().hex
        filename = f"{unique_id}{ext}"

        # Save the image locally
        local_filepath = os.path.join(save_directory, filename)
        with open(local_filepath, 'wb') as img_file:
            img_file.write(response.content)

        logger.info(f"Image downloaded and saved as: {filename}")
        return os.path.join('approvedimages', filename)  # Relative path
    except requests.RequestException as e:
        logger.error(f"Failed to download image from '{image_url}': {e}")
        return None
    except Exception as e:
        logger.error(f"Unexpected error during image download from '{image_url}': {e}")
        return None

def upload_image_to_firebase(word, local_image_path):
    """
    Uploads the image to Firebase Storage and updates Firestore with the image URL.
    Returns the Firebase image URL if successful, else None.
    """
    try:
        if not os.path.exists(local_image_path):
            logger.error(f"Local image file does not exist: {local_image_path}")
            return None

        # Validate file extension
        if not is_allowed_file(local_image_path):
            logger.error(f"Unsupported file extension for '{word}': {local_image_path}")
            return None

        # Validate file size
        if os.path.getsize(local_image_path) > MAX_IMAGE_SIZE:
            logger.error(f"Image size exceeds limit for '{word}': {local_image_path}")
            return None

        # Upload to Firebase Storage under 'dictionaryimages' folder
        filename = os.path.basename(local_image_path)
        blob = bucket.blob(f"dictionaryimages/{secure_filename(filename)}")
        blob.upload_from_filename(local_image_path)

        # Make the blob publicly accessible and get its URL
        blob.make_public()
        firebase_image_url = blob.public_url

        # Update the Firestore document
        doc_ref = db.collection('dictionary').document(word)
        doc_ref.set({"image": firebase_image_url}, merge=True)


        return firebase_image_url

    except Exception as e:
        logger.error(f"Error uploading image for '{word}' to Firebase: {e}")
        return None

# -------------------------------
# API Endpoints
# -------------------------------

@app.route('/api/words', methods=['GET'])
def get_words():
    """
    Returns all words from output2.json.
    """
    try:
        with file_lock:
            with open(OUTPUT_JSON, 'r', encoding='utf-8') as f:
                data = json.load(f)
        return jsonify(data), 200
    except FileNotFoundError:
        logger.error(f"{OUTPUT_JSON} not found.")
        return jsonify({"error": f"{OUTPUT_JSON} not found."}), 404
    except json.JSONDecodeError:
        logger.error(f"{OUTPUT_JSON} is invalid or empty.")
        return jsonify({"error": f"{OUTPUT_JSON} is invalid or empty."}), 400
    except Exception as e:
        logger.error(f"Error in /api/words: {e}")
        return jsonify({"error": str(e)}), 500

@app.route('/api/approve', methods=['POST'])
def approve_word():
    """
    Approves a specific word:
    1. Removes it from output2.json
    2. Downloads the image and saves it locally veya mevcutsa kullanır
    3. Adds the word to outputapproved.json
    4. Uploads the image to Firebase and updates Firestore
    """
    try:
        raw_data = request.get_data(as_text=True)


        # Parse JSON
        data = request.get_json()
        if not data:
            logger.error("Invalid JSON data or incorrect Content-Type.")
            return jsonify({"error": "Invalid JSON data."}), 400

        word = data.get('word')


        if not word or not isinstance(word, str) or not word.strip():
            logger.error("Word must be a non-empty string.")
            return jsonify({"error": "Word must be a non-empty string."}), 400

        with file_lock:
            # Load output2.json
            with open(OUTPUT_JSON, 'r', encoding='utf-8') as f:
                try:
                    data_json = json.load(f)
                except json.JSONDecodeError:
                    logger.error(f"{OUTPUT_JSON} is invalid JSON.")
                    return jsonify({"error": f"{OUTPUT_JSON} is invalid JSON."}), 400

            if word not in data_json:
                logger.error(f"'{word}' not found in {OUTPUT_JSON}.")
                return jsonify({"error": f"'{word}' not found in {OUTPUT_JSON}."}), 404

            word_data = data_json.pop(word)

            # Get image_url
            image_url = word_data.get('image')
            if not image_url:
                logger.error(f"No image URL found for '{word}'.")
                return jsonify({"error": f"No image URL found for '{word}'."}), 400

            # Check if image_url is a URL or a local path
            if is_url(image_url):
                # Attempt to download the image from URL
                local_image_path = download_image(image_url, APPROVED_IMAGES_DIR)
                if not local_image_path:
                    logger.error(f"Failed to download image for '{word}'.")
                    return jsonify({"error": f"Failed to download image for '{word}'."}), 500
            else:
                # Assume it's a local path relative to BASE_DIR
                local_image_full_path = os.path.join(BASE_DIR, image_url)
                if not os.path.exists(local_image_full_path):
                    logger.error(f"Local image file does not exist for '{word}': {local_image_full_path}")
                    return jsonify({"error": f"Local image file does not exist for '{word}'."}), 400
                # Ensure the image is in the approvedimages directory
                filename = os.path.basename(local_image_full_path)
                new_local_image_path = os.path.join(APPROVED_IMAGES_DIR, filename)
                # Move the image to approvedimages if it's not already there
                if not os.path.abspath(local_image_full_path).startswith(os.path.abspath(APPROVED_IMAGES_DIR)):
                    try:
                        os.rename(local_image_full_path, new_local_image_path)
                        logger.info(f"Moved image for '{word}' to '{new_local_image_path}'.")
                    except Exception as e:
                        logger.error(f"Failed to move image for '{word}': {e}")
                        return jsonify({"error": f"Failed to move image for '{word}'."}), 500
                local_image_path = os.path.join('approvedimages', filename)

            # Update the image path to the local path
            word_data['image'] = local_image_path

            # Load outputapproved.json
            with open(OUTPUT_APPROVED_JSON, 'r', encoding='utf-8') as f:
                try:
                    approved_data = json.load(f)
                except json.JSONDecodeError:
                    approved_data = {}
                    logger.warning(f"{OUTPUT_APPROVED_JSON} is invalid JSON. Reinitializing as empty.")

            if not isinstance(approved_data, dict):
                approved_data = {}

            # Add the word to approved_data
            approved_data[word] = word_data

            # Atomically write approved_data
            atomic_write(OUTPUT_APPROVED_JSON, approved_data)

            # Atomically write updated data_json
            atomic_write(OUTPUT_JSON, data_json)



        # Upload image to Firebase synchronously
        firebase_image_url = upload_image_to_firebase(word, os.path.join(BASE_DIR, local_image_path))
        if firebase_image_url:
            # Update the local approved_data with the Firebase image URL
            with file_lock:
                with open(OUTPUT_APPROVED_JSON, 'r', encoding='utf-8') as f:
                    approved_data = json.load(f)
                approved_data[word]['image'] = firebase_image_url
                atomic_write(OUTPUT_APPROVED_JSON, approved_data)
            logger.info(f"Image for '{word}' updated with Firebase URL.")
            # Optionally, delete the local image file after successful upload
            local_full_path = os.path.join(BASE_DIR, local_image_path)
            try:
                os.remove(local_full_path)
                logger.info(f"Local image file '{local_image_path}' has been deleted after upload.")
            except Exception as e:
                logger.error(f"Failed to delete local image file '{local_image_path}': {e}")
        else:
            # If upload failed, you might want to handle it (e.g., notify admin)
            logger.warning(f"Image for '{word}' failed to upload to Firebase.")

        return jsonify({"message": f"'{word}' has been approved and image uploaded."}), 200

    except Exception as e:
        logger.error(f"Error in /api/approve: {e}")
        return jsonify({"error": "An unexpected error occurred.", "details": str(e)}), 500

@app.route('/api/delete', methods=['POST'])
def delete_word():
    """
    Deletes a specific word from output2.json.
    """
    try:
        raw_data = request.get_data(as_text=True)
        logger.info(f"Received delete request data: {raw_data}")

        # Parse JSON
        data = request.get_json()
        if not data:
            logger.error("Invalid JSON data or incorrect Content-Type.")
            return jsonify({"error": "Invalid JSON data."}), 400

        word = data.get('word')
        if not word or not isinstance(word, str) or not word.strip():
            logger.error("Word must be a non-empty string.")
            return jsonify({"error": "Word must be a non-empty string."}), 400

        with file_lock:
            # Load output2.json
            with open(OUTPUT_JSON, 'r', encoding='utf-8') as f:
                try:
                    data_json = json.load(f)
                except json.JSONDecodeError:
                    logger.error(f"{OUTPUT_JSON} is invalid JSON.")
                    return jsonify({"error": f"{OUTPUT_JSON} is invalid JSON."}), 400

            if word not in data_json:
                logger.error(f"'{word}' not found in {OUTPUT_JSON}.")
                return jsonify({"error": f"'{word}' not found in {OUTPUT_JSON}."}), 404

            data_json.pop(word)

            # Atomically write updated data_json
            atomic_write(OUTPUT_JSON, data_json)


        return jsonify({"message": f"'{word}' has been deleted."}), 200

    except Exception as e:
        logger.error(f"Error in /api/delete: {e}")
        return jsonify({"error": str(e)}), 500

@app.route('/api/download_all_pollinations_images', methods=['POST'])
def download_all_pollinations_images():
    """
    Downloads all Pollinations.ai image URLs from output2.json, saves them locally,
    updates output2.json with the local paths.
    """
    def download_images():
        try:
            with file_lock:
                with open(OUTPUT_JSON, 'r', encoding='utf-8') as f:
                    try:
                        data_json = json.load(f)
                    except json.JSONDecodeError:
                        logger.error(f"{OUTPUT_JSON} is invalid JSON.")
                        return

            if not data_json:
                logger.info("output2.json is empty.")
                return

            updated = False  # Flag to check if any updates are made

            for word, details in data_json.items():
                image_url = details.get('image')
                if not image_url:
                    logger.warning(f"No image URL for '{word}'.")
                    continue

                # Skip if already downloaded
                if image_url.startswith('approvedimages/'):
                    logger.info(f"Image for '{word}' is already downloaded: {image_url}")
                    continue

                if not is_url(image_url):
                    logger.warning(f"Invalid image URL format for '{word}': {image_url}")
                    continue

                # Download and save the image locally
                local_image_path = download_image(image_url, APPROVED_IMAGES_DIR)
                if not local_image_path:
                    logger.error(f"Failed to download image for '{word}'.")
                    continue

                # Update the image path in data_json
                with file_lock:
                    data_json[word]['image'] = local_image_path
                    atomic_write(OUTPUT_JSON, data_json)
                updated = True

            if updated:
                logger.info("All Pollinations.ai images have been downloaded and output2.json updated.")
            else:
                logger.info("No new images were downloaded.")

        except Exception as e:
            logger.error(f"Error in download_images: {e}")

    # Start the download process in a new thread
    threading.Thread(target=download_images).start()
    return jsonify({"message": "Image download started in the background."}), 200

@app.route('/api/upload_to_firebase', methods=['POST'])
def upload_to_firebase_endpoint():
    """
    Uploads images from outputapproved.json to Firebase Storage,
    updates Firestore documents, and removes the image entries from outputapproved.json.
    """
    try:
        with file_lock:
            if not os.path.exists(OUTPUT_APPROVED_JSON):
                logger.error(f"{OUTPUT_APPROVED_JSON} not found.")
                return jsonify({"error": f"{OUTPUT_APPROVED_JSON} not found."}), 404

            with open(OUTPUT_APPROVED_JSON, 'r', encoding='utf-8') as f:
                try:
                    approved_data = json.load(f)
                except json.JSONDecodeError:
                    approved_data = {}
                    logger.warning(f"{OUTPUT_APPROVED_JSON} is invalid JSON. Reinitializing as empty.")

        if not approved_data:
            logger.warning("No approved images to upload.")
            return jsonify({"error": "No approved images to upload."}), 400

        success_uploads = []
        failed_uploads = []

        for word, details in approved_data.items():
            image_path = details.get('image')
            if not image_path:
                logger.warning(f"No image path for '{word}'.")
                continue

            # Ensure the image is local
            if is_url(image_path):
                logger.warning(f"Image for '{word}' is a URL, expected local path: {image_path}")
                continue

            if not image_path.startswith('approvedimages/'):
                logger.warning(f"Image for '{word}' is not in 'approvedimages' directory: {image_path}")
                continue

            local_image_full_path = os.path.join(BASE_DIR, image_path)
            if not os.path.exists(local_image_full_path):
                logger.error(f"Local image file not found for '{word}': {local_image_full_path}")
                failed_uploads.append({"word": word, "error": "Image file not found."})
                continue

            try:
                # Upload to Firebase Storage
                firebase_image_url = upload_image_to_firebase(word, local_image_full_path)
                if firebase_image_url:
                    # Mark for removal from approved_data
                    success_uploads.append(word)
                else:
                    failed_uploads.append({"word": word, "error": "Failed to upload to Firebase."})
            except Exception as e:
                logger.error(f"Failed to upload image for '{word}' to Firebase: {e}")
                failed_uploads.append({"word": word, "error": str(e)})

        # Remove successfully uploaded words from approved_data
        with file_lock:
            for word in success_uploads:
                approved_data.pop(word, None)
            atomic_write(OUTPUT_APPROVED_JSON, approved_data)

        response = {
            "message": "Firebase upload process completed.",
            "success_uploads": success_uploads,
            "failed_uploads": failed_uploads
        }

        return jsonify(response), 200

    except Exception as e:
        logger.error(f"Error in /api/upload_to_firebase: {e}")
        return jsonify({"error": str(e)}), 500

@app.route('/approvedimages/<filename>', methods=['GET'])
def serve_image(filename):
    """
    Serves images from the approvedimages directory.
    """
    try:
        safe_filename = secure_filename(filename)
        return send_from_directory(APPROVED_IMAGES_DIR, safe_filename, as_attachment=False)
    except FileNotFoundError:
        logger.error(f"File not found: {filename}")
        return jsonify({"error": "File not found."}), 404
    except Exception as e:
        logger.error(f"Error serving image '{filename}': {e}")
        return jsonify({"error": "Internal server error."}), 500

@app.route('/', methods=['GET'])
def index():
    """
    Serves the index.html file.
    """
    try:
        return send_from_directory(BASE_DIR, 'index.html')
    except Exception as e:
        logger.error(f"Error serving index.html: {e}")
        return jsonify({"error": "Internal server error."}), 500

@app.route('/<path:path>', methods=['GET'])
def static_proxy(path):
    """
    Serves static files (CSS, JS, etc.).
    """
    try:
        return send_from_directory(BASE_DIR, path)
    except FileNotFoundError:
        logger.error(f"Static file not found: {path}")
        return jsonify({"error": "File not found."}), 404
    except Exception as e:
        logger.error(f"Error serving static file '{path}': {e}")
        return jsonify({"error": "Internal server error."}), 500

# -------------------------------
# Main Entry Point
# -------------------------------

if __name__ == '__main__':
    # It's recommended to set debug=False in production
    app.run(host='0.0.0.0', port=5000, debug=True) burada approve işleminde sıkıntı var. kelime onaylandığında onaylanan kelime için şu işlem olacak
    firebaseye yükleme: linkteki resim indirilip firebase firestorageye yüklenecek.
    firebaseye yüklenen resmin linki alınacak ve şu şekilde firebaseye eklenecek:
/dictionary/'بَيْتِ:

{
  "exampleSentences": [
    {
      "arabic": "يُوصِي المُؤمِنُ أَهْلَهُ بِتَقْوَى اللَّهِ فِي بَيْتِهِ.",
      "english": "The believer instructs his family to be mindful of Allah in their home.",
      "sentence_no": 1,
      "turkish": "Mümin, ailesine evinde Allah'a karşı takva sahibi olmaları konusunda öğüt verir."
    },
    {
      "arabic": "يَجْعَلُ الْمُسْلِمُ بَيْتَهُ مَكَانًا لِلتَّعَلُّمِ وَالتَّذَكُّرِ.",
      "english": "A Muslim makes their home a place of learning and remembrance.",
      "sentence_no": 2,
      "turkish": "Müslüman, evini öğrenme ve hatırlama yeri yapar."
    }
  ],
  "image": "https://storage.googleapis.com/readarabic-dec46.appspot.com/dictionaryimages/99612e3f048e4e7ab71e621c46a77ce8.jpg",
  "meaningEN": [
    "house",
    "home",
    "dwelling"
  ],
  "meaningTR": [
    "ev",
    "yuva",
    "konut"
  ],
  "root": "ب-ي-ت",
  "word": "بَيْتِ",
  "wordTypeEN": "Noun",
  "wordTypeTR": "isim"
}
gibi. image linki eklenecek firestorageye yüklenen. bunu anlık olarak arkaplanda yapacak.

örnek output2.json dosyası şöyle: buradaki word değeri, firebaseye yüklenirken kullanılacak dictionary/$word şeklinde olacak.

output2.json:
   "إجراء": {
        "word": "إجراء",
        "meaningEN": [
            "action",
            "measure",
            "procedure",
            "step"
        ],
        "image": "https://image.pollinations.ai/prompt/Create%20an%20image%20related%20with%20these%20keywords%20%27action%2C%20measure%27%20do%20not%20add%20any%20keyword%20text%20to%20the%20image%20just%20create%20an%20image%20related%20with%20the%20keywords.%20Make%20the%20image%20visually%20interesting.%20Do%20not%20include%20any%20text%20or%20writing%20of%20any%20kind.?model=flux-realism&seed=-1&width=512&height=512&nologo=true&private=true&enhance=true"
    },
    "الأَمْطَارِ": {
        "word": "الأَمْطَارِ",
        "meaningEN": [
            "rains",
            "showers"
        ],
        "image": "https://image.pollinations.ai/prompt/Create%20an%20image%20related%20with%20these%20keywords%20%27rains%2C%20showers%27%20do%20not%20add%20any%20keyword%20text%20to%20the%20image%20just%20create%20an%20image%20related%20with%20the%20keywords.%20Make%20the%20image%20visually%20interesting.%20Do%20not%20include%20any%20text%20or%20writing%20of%20any%20kind.?model=flux-realism&seed=-1&width=512&height=512&nologo=true&private=true&enhance=true"
    },
    "الأَنْوَاعِ": {
        "word": "الأَنْوَاعِ",
        "meaningEN": [
            "kinds",
            "types",
            "species",
            "varieties",
            "sorts"
        ],
        "image": "https://image.pollinations.ai/prompt/Create%20an%20image%20related%20with%20these%20keywords%20%27kinds%2C%20types%27%20do%20not%20add%20any%20keyword%20text%20to%20the%20image%20just%20create%20an%20image%20related%20with%20the%20keywords.%20Make%20the%20image%20visually%20interesting.%20Do%20not%20include%20any%20text%20or%20writing%20of%20any%20kind.?model=flux-realism&seed=-1&width=512&height=512&nologo=true&private=true&enhance=true"
    },....
    tekrar anlatıyorum, bir resim onaylandığında o resim arkaplanda indirilecek ve belirtilen formatta firebase firestorageye kaydedilecek. oradan dönen public linkte dictionary/$word içerisinde image değeri olarak kaydedilecek. loglamayı detaylı yap