import json
import os

from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for
from flask_login import current_user, login_required
from sqlalchemy import select
from werkzeug.utils import secure_filename

from web_application import db
from web_application.constants import (
    ALLOWED_EXTENSIONS,
    EXCEL_REPORTS,
    YOLO_UPLOAD_FOLDER,
)
from web_application.models import (
    YoloAIAssessment,
    YoloDrAIComparison,
    YoloDrAssessment,
    YoloImage,
    YoloPatient,
)

yolo = Blueprint(
    "yolo",
    __name__,
    template_folder="templates",
    static_folder="static",
    url_prefix="/yolo",
)

REQUIRED_FILE_COUNT = 9


def get_or_create_yolo_patient(case_dir_name):
    stmt = select(YoloPatient).where(YoloPatient.case_dir == case_dir_name)
    existing_patient = db.session.execute(stmt).scalars().first()

    if existing_patient:
        return existing_patient

    yolo_patient = YoloPatient(case_dir=case_dir_name, status=1)
    db.session.add(yolo_patient)
    db.session.commit()
    return yolo_patient


def create_basic_dirs(case_dir_name):
    # Use os.path.join correctly to create the path string
    case_dir = os.path.join(YOLO_UPLOAD_FOLDER, case_dir_name)
    original_images_dir = os.path.join(case_dir, "original")
    marked_images_dir = os.path.join(case_dir, "marked")

    # Create the directories if they don't exist
    os.makedirs(original_images_dir, exist_ok=True)
    os.makedirs(marked_images_dir, exist_ok=True)

    return original_images_dir, marked_images_dir


def process_and_save_files(file_list, target_path, patient_id, marked_status):
    """Saves files to disk and creates DB records.
    Optimized to avoid duplicate image records for the same filename/patient.
    """
    os.makedirs(target_path, exist_ok=True)

    for file in file_list:
        safe_name = secure_filename(file.filename)
        file.save(os.path.join(target_path, safe_name))

        # Check if image already exists in DB to prevent duplicates
        stmt = select(YoloImage).where(
            YoloImage.yolo_patient_id == patient_id,
            YoloImage.filename == safe_name,
            YoloImage.is_marked == marked_status,
        )
        existing_img = db.session.execute(stmt).scalars().first()

        if not existing_img:
            # Create SQLAlchemy instance
            new_img = YoloImage(
                yolo_patient_id=patient_id, filename=safe_name, is_marked=marked_status
            )
            db.session.add(new_img)


def allowed_file(filename):
    return os.path.splitext(filename)[1].lower() in ALLOWED_EXTENSIONS


def save_file_list(files, target_subfolder):
    os.makedirs(target_subfolder, exist_ok=True)
    for file in files:
        filename = secure_filename(file.filename)
        file.save(os.path.join(target_subfolder, filename))


@yolo.route("/")
@yolo.route("/index")
@yolo.route("/home")
@login_required
def home():
    """
    YOLO Dashboard: Lists all patient cases.
    Checks if the current doctor has assessed each case.
    """
    # 1. Fetch all YoloPatients sorted by creation date (newest first)
    stmt = select(YoloPatient).order_by(YoloPatient.created_at.desc())
    patients = db.session.execute(stmt).scalars().all()

    # 2. Prepare presentation data with assessment status
    cases_data = []
    for patient in patients:
        # Check if current user has an assessment for this patient
        stmt_assessment = select(YoloDrAssessment).where(
            YoloDrAssessment.yolo_patient_id == patient.id,
            YoloDrAssessment.user_id == current_user.id,
        )
        existing_assessment = db.session.execute(stmt_assessment).scalars().first()

        cases_data.append(
            {
                "data": patient,
                "is_assessed": existing_assessment is not None,
                "assessment_date": (
                    existing_assessment.updated_at if existing_assessment else None
                ),
            }
        )

    # Sort cases_data: unassessed (False) first, then assessed (True).
    # Since the initial fetch is ordered by created_at desc, and Python's sort is stable,
    # secondary ordering (by date) is preserved.
    cases_data.sort(key=lambda x: x["is_assessed"])

    context = {
        "title": "YOLO Vaka Listesi",
        "cases": cases_data,
    }

    return render_template("yolo/home.html", **context)


@yolo.route("/upload", methods=["POST"])
def upload():
    # 1. Retrieve and Parse the Dictionary
    meta_data_str = request.form.get("meta_data")
    if not meta_data_str:
        return jsonify({"error": "Missing metadata dictionary"}), 400

    try:
        meta_dict = json.loads(meta_data_str)
    except json.JSONDecodeError:
        return jsonify({"error": "Invalid JSON format in metadata"}), 400

    case_dir_name = meta_dict.get("case_dir_name", "default_case")

    # UPSERT Logic for Patient
    yolo_patient = get_or_create_yolo_patient(case_dir_name)

    original_images_dir, marked_images_dir = create_basic_dirs(case_dir_name)
    # 2. Get both image lists
    original_images_list = request.files.getlist("original_images")
    marked_images_list = request.files.getlist("marked_images")

    # 3. Strict Validation
    if len(original_images_list) != 9 or len(marked_images_list) != 9:
        return jsonify({"error": "Both lists must contain exactly 9 images"}), 400

    # 4. Save to separate subdirectories
    try:
        # Save Originals (is_marked = 0)
        process_and_save_files(
            original_images_list, original_images_dir, yolo_patient.id, 0
        )

        # Save Marked (is_marked = 1)
        process_and_save_files(
            marked_images_list, marked_images_dir, yolo_patient.id, 1
        )

        # Final Commit for images
        db.session.commit()

    except Exception as e:
        db.session.rollback()
        return jsonify({"error": f"Database error (Images): {str(e)}"}), 500

    # 5. AI Assessment
    try:
        ai_assessment = (
            db.session.execute(
                select(YoloAIAssessment).where(
                    YoloAIAssessment.case_dir == case_dir_name
                )
            )
            .scalars()
            .first()
        )

        if ai_assessment:
            # Update existing AI assessment
            ai_assessment.yolo_patient_id = (
                yolo_patient.id
            )  # Ensure ID link is correct/updated
            ai_assessment.pnx = meta_dict.get("pnx")
            ai_assessment.hmx = meta_dict.get("hmx")
            ai_assessment.akk = meta_dict.get("akk")
            ai_assessment.kot = meta_dict.get("kot")
            ai_assessment.updated_at = db.func.current_timestamp()

            db.session.commit()
            return jsonify(
                {
                    "status": "success",
                    "received_meta": meta_dict,
                    "message": "AI assessment updated successfully.",
                }
            ), 200
        else:
            # Create new AI assessment
            ai_assessment = YoloAIAssessment(
                yolo_patient_id=yolo_patient.id,
                case_dir=yolo_patient.case_dir,
                pnx=meta_dict.get("pnx"),
                hmx=meta_dict.get("hmx"),
                akk=meta_dict.get("akk"),
                kot=meta_dict.get("kot"),
            )
            db.session.add(ai_assessment)
            db.session.commit()

            return jsonify(
                {
                    "status": "success",
                    "received_meta": meta_dict,
                    "message": "Both image sets and metadata saved successfully.",
                }
            ), 200

    except Exception as e:
        db.session.rollback()
        return jsonify({"error": f"Database error (AI Assessment): {str(e)}"}), 500


@yolo.route("/detay/<int:id>")
@login_required
def detay(id):
    """
    Case Detail Page:
    - Shows Original and Marked images.
    - Shows AI Analysis results.
    - Shows Doctor's existing assessment (if any) to pre-fill the form.
    - Shows Comparison result if exists.
    """
    # 1. Fetch Patient
    stmt_patient = select(YoloPatient).where(YoloPatient.id == id)
    patient = db.session.execute(stmt_patient).scalars().first()
    if not patient:
        flash("Vaka bulunamadı.", "danger")
        return redirect(url_for("yolo.home"))

    # stmt_patient = select(YoloPatient).where(YoloPatient.id == id)
    # patient = db.session.execute(stmt_patient).scalars().first()

    # if not patient:
    #     return render_template("404.html"), 404

    # 2. Fetch Images (Separated by Type)
    all_images = patient.get_images()
    original_images = [img for img in all_images if img.is_marked == 0]
    marked_images = [img for img in all_images if img.is_marked == 1]

    # 3. Fetch AI Assessment
    stmt_ai = select(YoloAIAssessment).where(YoloAIAssessment.yolo_patient_id == id)
    ai_result = db.session.execute(stmt_ai).scalars().first()

    # 3. Fetch Doctor's Existing Assessment (for current user)
    stmt_dr = select(YoloDrAssessment).where(
        YoloDrAssessment.yolo_patient_id == id,
        YoloDrAssessment.user_id == current_user.id,
    )
    dr_assessment = db.session.execute(stmt_dr).scalars().first()

    # 4. Fetch Dr-AI Comparison (Specific to current user)
    stmt_comp = select(YoloDrAIComparison).where(
        YoloDrAIComparison.yolo_patient_id == id,
        YoloDrAIComparison.user_id == current_user.id,
    )
    dr_ai_comparison = db.session.execute(stmt_comp).scalars().first()

    # --- AUTO-FIX / LOGIC EXTENSION ---
    # If Doctor has assessed but Comparison is missing (Legacy data or Logic gap),
    # generate it on the fly to ensure Role 2/3 users see the Result screen.
    if dr_assessment and not dr_ai_comparison and ai_result:
        comp_pnx = 1 if dr_assessment.pnx == ai_result.pnx else 3
        comp_hmx = 1 if dr_assessment.hmx == ai_result.hmx else 3
        comp_akk = 1 if dr_assessment.akk == ai_result.akk else 3
        comp_kot = 1 if dr_assessment.kot == ai_result.kot else 3

        dr_ai_comparison = YoloDrAIComparison(
            user_id=current_user.id,
            yolo_patient_id=id,
            case_dir=patient.case_dir,
            pnx=comp_pnx,
            hmx=comp_hmx,
            akk=comp_akk,
            kot=comp_kot,
        )
        db.session.add(dr_ai_comparison)
        db.session.commit()
    # ----------------------------------

    context = {
        "title": f"Vaka Detayı: {patient.case_dir}",
        "patient": patient,
        "original_images": original_images,
        "marked_images": marked_images,
        "ai_result": ai_result,
        "dr_assessment": dr_assessment,
        "dr_ai_comparison": dr_ai_comparison,
    }

    return render_template("yolo/detay.html", **context)


@yolo.route("/degerlendirme_kaydet/<int:id>", methods=["POST"])
@login_required
def degerlendirme_kaydet(id):
    """
    Saves the Doctor's assessment for a specific case.
    Comparison logic:
    - Intercept save.
    - Validate uniqueness (cannot update existing).
    - Fetch matching AI assessment.
    - Compare Dr inputs vs AI inputs.
    - Save YoloDrAIComparison (1=Match, 3=Mismatch) tied to this user.
    """
    # 1. Fetch Patient validity
    stmt_patient = select(YoloPatient).where(YoloPatient.id == id)
    patient = db.session.execute(stmt_patient).scalars().first()

    if not patient:
        return jsonify({"error": "Vaka bulunamadı."}), 404

    # 2. Get Form Data (convert to int, default 0)
    try:
        pnx = int(request.form.get("pnx", 0))
        hmx = int(request.form.get("hmx", 0))
        akk = int(request.form.get("akk", 0))
        kot = int(request.form.get("kot", 0))
    except ValueError:
        return jsonify({"error": "Geçersiz veri formatı."}), 400

    try:
        # 3. Check for existing assessment to prevent update (Business Rule: Benzersizlik/Immutable)
        stmt_dr = select(YoloDrAssessment).where(
            YoloDrAssessment.yolo_patient_id == id,
            YoloDrAssessment.user_id == current_user.id,
        )
        existing_assessment = db.session.execute(stmt_dr).scalars().first()

        if existing_assessment:
            # Prevent update as per requirement
            return (
                jsonify(
                    {
                        "error": "Bu vaka için daha önce değerlendirme yaptınız. Güncelleme yapılamaz."
                    }
                ),
                403,
            )

        # Create new assessment
        dr_assessment = YoloDrAssessment(
            user_id=current_user.id,
            yolo_patient_id=id,
            case_dir=patient.case_dir,
            pnx=pnx,
            hmx=hmx,
            akk=akk,
            kot=kot,
        )
        db.session.add(dr_assessment)
        db.session.flush()

        # --- COMPARISON LOGIC ---
        # Fetch AI Assessment
        stmt_ai = select(YoloAIAssessment).where(YoloAIAssessment.yolo_patient_id == id)
        ai_assessment = db.session.execute(stmt_ai).scalars().first()

        if ai_assessment:
            # Logic: 1 if match, 3 if mismatch
            comp_pnx = 1 if pnx == ai_assessment.pnx else 3
            comp_hmx = 1 if hmx == ai_assessment.hmx else 3
            comp_akk = 1 if akk == ai_assessment.akk else 3
            comp_kot = 1 if kot == ai_assessment.kot else 3

            # Create comparison record for THIS user
            # Since updates are not allowed, we always create fresh for the comparison too
            comparison = YoloDrAIComparison(
                user_id=current_user.id,
                yolo_patient_id=id,
                case_dir=patient.case_dir,
                pnx=comp_pnx,
                hmx=comp_hmx,
                akk=comp_akk,
                kot=comp_kot,
            )
            db.session.add(comparison)

        # ------------------------

        db.session.commit()
        patient.status = 2
        db.session.commit()
        # Update patient status if needed (e.g. mark as assessed)
        # Note: In a multi-doctor scenario, status=3 might mean "assessed by AT LEAST one doctor".
        # We can keep it simple for now or check independent statuses.
        # if patient.status != 3:
        #     patient.status = 3
        #     db.session.commit()

        # 4. Redirect back to detail
        return redirect(url_for("yolo.detay", id=id))

    except Exception as e:
        db.session.rollback()
        return jsonify({"error": f"Veritabanı hatası: {str(e)}"}), 500


import os

import pandas as pd


@yolo.route("/export")
@login_required
def export():
    target_directory = os.path.join(EXCEL_REPORTS)
    if not os.path.exists(target_directory):
        os.makedirs(target_directory)

    patients = (
        db.session.query(
            YoloPatient.case_dir,
            # AI Assessment - ai_ prefixi
            YoloAIAssessment.pnx.label("ai_pnx"),
            YoloAIAssessment.hmx.label("ai_hmx"),
            YoloAIAssessment.akk.label("ai_akk"),
            YoloAIAssessment.kot.label("ai_kot"),
            # Dr Assessment - dr_ prefixi
            YoloDrAssessment.pnx.label("dr_pnx"),
            YoloDrAssessment.hmx.label("dr_hmx"),
            YoloDrAssessment.akk.label("dr_akk"),
            YoloDrAssessment.kot.label("dr_kot"),
            YoloDrAssessment.user_id.label("user_id"),
            # Dr AI Comparison - comp_ prefixi
            YoloDrAIComparison.pnx.label("comp_pnx"),
            YoloDrAIComparison.hmx.label("comp_hmx"),
            YoloDrAIComparison.akk.label("comp_akk"),
            YoloDrAIComparison.kot.label("comp_kot"),
        )
        .join(YoloAIAssessment, YoloAIAssessment.yolo_patient_id == YoloPatient.id)
        .join(YoloDrAssessment, YoloDrAssessment.yolo_patient_id == YoloPatient.id)
        .join(YoloDrAIComparison, YoloDrAIComparison.yolo_patient_id == YoloPatient.id)
        .order_by(YoloAIAssessment.id.asc())
        .all()
    )
    data = export_to_excel(patients)
    df = pd.DataFrame(data)

    # df = df.rename(columns=COLUMN_NAMES)

    file_name = "diagnosis_yolo_report.xlsx"
    file_path = os.path.join(target_directory, file_name)
    df.to_excel(file_path, index=False)

    data = {
        "title": "Rapor İndir",
        "is_created": 1 if len(patients) > 0 else 0,
    }

    return render_template("yolo/export.html", **data)


def export_to_excel(patients):
    data = {
        "case_dir": [],
        "ai_pnx": [],
        "ai_hmx": [],
        "ai_akk": [],
        "ai_kot": [],
        "dr_pnx": [],
        "dr_hmx": [],
        "dr_akk": [],
        "dr_kot": [],
        "comp_pnx": [],
        "comp_hmx": [],
        "comp_akk": [],
        "comp_kot": [],
        "user_id": [],
    }

    for patient in patients:
        data["case_dir"].append(patient.case_dir)
        data["ai_pnx"].append(patient.ai_pnx)
        data["ai_hmx"].append(patient.ai_hmx)
        data["ai_akk"].append(patient.ai_akk)
        data["ai_kot"].append(patient.ai_kot)
        data["dr_pnx"].append(patient.dr_pnx)
        data["dr_hmx"].append(patient.dr_hmx)
        data["dr_akk"].append(patient.dr_akk)
        data["dr_kot"].append(patient.dr_kot)
        data["comp_pnx"].append(patient.comp_pnx)
        data["comp_hmx"].append(patient.comp_hmx)
        data["comp_akk"].append(patient.comp_akk)
        data["comp_kot"].append(patient.comp_kot)
        data["user_id"].append(patient.user_id)

    return data
