# routes/assets.py
from __future__ import annotations

from datetime import datetime, date
from flask import (
    Blueprint, render_template, request, redirect,
    url_for, flash, current_app
)
from flask_login import login_required, current_user
from sqlalchemy import or_, func

from extensions import db
from models import (
    Asset, AssetCategory, AssetVendor,
    AssetAllocation, AssetLossDamage,
    Employee
)

assets_bp = Blueprint("assets", __name__, url_prefix="/assets")


# ---------- Helpers ----------

def _parse_date(val: str):
    if not val:
        return None
    try:
        return datetime.strptime(val, "%Y-%m-%d").date()
    except Exception:
        return None


# ---------- Asset Master ----------

@assets_bp.route("/", methods=["GET", "POST"])
@login_required
def list_assets():
    """
    Asset master page:
    - GET: list assets + filters
    - POST: create / update asset (from modal)
    """
    if request.method == "POST":
        mode = request.form.get("mode")  # "create" or "edit"
        asset_id = request.form.get("asset_id")

        # Optional explicit asset_code
        asset_code = (request.form.get("asset_code") or "").strip()

        asset_tag = (request.form.get("asset_tag") or "").strip()
        name = (request.form.get("name") or "").strip()
        category_id = request.form.get("category_id") or None
        vendor_id = request.form.get("vendor_id") or None
        serial_number = (request.form.get("serial_number") or "").strip()
        purchase_date = _parse_date(request.form.get("purchase_date"))
        warranty_expiry = _parse_date(request.form.get("warranty_expiry"))
        status = (request.form.get("status") or "in_stock").strip()
        location = (request.form.get("location") or "").strip()
        remarks = (request.form.get("remarks") or "").strip()

        if not asset_tag or not name:
            flash("Asset Tag and Name are required.", "danger")
            return redirect(url_for("assets.list_assets"))

        try:
            if mode == "edit" and asset_id:
                asset = Asset.query.get_or_404(int(asset_id))
            else:
                asset = Asset()
                db.session.add(asset)

            # Ensure asset_code is always non-null
            if asset_code:
                asset.asset_code = asset_code
            elif not getattr(asset, "asset_code", None):
                asset.asset_code = asset_tag

            asset.asset_tag = asset_tag
            asset.name = name
            asset.category_id = int(category_id) if category_id else None
            asset.vendor_id = int(vendor_id) if vendor_id else None
            asset.serial_number = serial_number
            asset.purchase_date = purchase_date
            asset.warranty_expiry = warranty_expiry
            asset.status = status
            asset.location = location
            asset.remarks = remarks

            db.session.commit()
            flash("Asset saved successfully.", "success")
        except Exception as e:
            current_app.logger.exception("Failed to save asset")
            db.session.rollback()
            flash(f"Error saving asset: {e}", "danger")

        return redirect(url_for("assets.list_assets"))

    # ---- GET: filters + listing ----
    q = (request.args.get("q") or "").strip()
    status = (request.args.get("status") or "").strip()
    category_id = request.args.get("category_id") or None

    query = Asset.query

    if q:
        like = f"%{q}%"
        query = query.filter(or_(
            Asset.asset_tag.ilike(like),
            Asset.name.ilike(like),
            Asset.serial_number.ilike(like)
        ))

    if status:
        query = query.filter(Asset.status == status)

    if category_id:
        query = query.filter(Asset.category_id == int(category_id))

    assets = (
        query
        .order_by(Asset.asset_tag.asc())
        .all()
    )

    categories = AssetCategory.query.order_by(AssetCategory.name.asc()).all()
    vendors = AssetVendor.query.order_by(AssetVendor.name.asc()).all()
    employees = Employee.query.order_by(Employee.full_name.asc()).all()

    # Assets that have an active allocation or a pending return
    active_alloc_asset_ids = {
        aid for (aid,) in db.session.query(AssetAllocation.asset_id)
        .filter(AssetAllocation.status.in_(["allocated", "return_pending"]))
        .distinct()
    }

    # Assets that have a pending loss / damage record
    pending_ld_asset_ids = {
        aid for (aid,) in db.session.query(AssetLossDamage.asset_id)
        .filter(AssetLossDamage.status == "pending")
        .distinct()
    }

    blocked_for_allocation = active_alloc_asset_ids | pending_ld_asset_ids

    return render_template(
        "assets/asset_master.html",
        assets=assets,
        categories=categories,
        vendors=vendors,
        q=q,
        status=status,
        category_id=category_id,
        employees=employees,
        now=datetime.utcnow,
        blocked_for_allocation=blocked_for_allocation,
    )


# ---------- Allocation / Return ----------

@assets_bp.route("/allocate", methods=["POST"])
@login_required
def allocate_asset():
    """
    Allocate asset to an employee.
    Asset id comes from hidden form field `asset_id`.
    """
    asset_id = request.form.get("asset_id")
    if not asset_id:
        flash("Missing asset id for allocation.", "danger")
        return redirect(url_for("assets.list_assets"))

    asset = Asset.query.get_or_404(int(asset_id))

    employee_id = request.form.get("employee_id")
    allocated_on = _parse_date(request.form.get("allocated_on")) or datetime.utcnow().date()
    expected_return_date = _parse_date(request.form.get("due_back_on"))
    remarks = (request.form.get("remarks") or "").strip()
    condition_on_issue = (request.form.get("condition_on_issue") or "").strip()

    if not employee_id:
        flash("Please choose an employee to allocate.", "danger")
        return redirect(url_for("assets.list_assets"))

    emp = Employee.query.get(int(employee_id))
    if not emp:
        flash("Invalid employee selected.", "danger")
        return redirect(url_for("assets.list_assets"))

    try:
        allocation = AssetAllocation(
            asset_id=asset.id,
            employee_id=emp.id,
            allocated_on=allocated_on,
            expected_return_date=expected_return_date,
            remarks=remarks,
            condition_on_issue=condition_on_issue,
            status="allocated",
        )

        asset.status = "allocated"

        db.session.add(allocation)
        db.session.commit()
        flash(f"Asset {asset.asset_tag} allocated to {emp.full_name}.", "success")
    except Exception as e:
        db.session.rollback()
        current_app.logger.exception("Failed to allocate asset")
        flash(f"Error allocating asset: {e}", "danger")

    return redirect(url_for("assets.list_assets"))


@assets_bp.route("/return/<int:allocation_id>", methods=["POST"])
@login_required
def return_asset(allocation_id):
    alloc = AssetAllocation.query.get_or_404(allocation_id)
    asset = alloc.asset

    ret_date = _parse_date(request.form.get("returned_on")) or datetime.utcnow().date()
    remarks = (request.form.get("return_remarks") or "").strip()

    try:
        # Store what the employee/admin is declaring,
        # but DO NOT free the asset yet.
        alloc.actual_return_date = ret_date
        alloc.condition_on_return = remarks
        alloc.status = "return_pending"

        db.session.commit()
        flash(
            f"Return request raised for asset {asset.asset_tag}. "
            "It will be updated in stock after approval.",
            "success",
        )
    except Exception as e:
        db.session.rollback()
        current_app.logger.exception("Failed to create return request")
        flash(f"Error creating return request: {e}", "danger")

    return redirect(url_for("assets.allocations"))


# ---------- Allocation History List ----------

@assets_bp.route("/allocations")
@login_required
def allocations():
    q = (request.args.get("q") or "").strip()
    status = (request.args.get("status") or "").strip()

    query = (
        AssetAllocation.query
        .join(Asset, AssetAllocation.asset_id == Asset.id)
        .join(Employee, AssetAllocation.employee_id == Employee.id)
    )

    if q:
        like = f"%{q}%"
        query = query.filter(or_(
            Asset.asset_tag.ilike(like),
            Asset.name.ilike(like),
            Employee.full_name.ilike(like),
            Employee.employee_code.ilike(like),
        ))

    if status:
        query = query.filter(AssetAllocation.status == status)

    allocations = (
        query
        .order_by(AssetAllocation.allocated_on.desc())
        .all()
    )

    return render_template(
        "assets/allocations.html",
        allocations=allocations,
        q=q,
        status=status,
    )


# ---------- Loss / Damage ----------

@assets_bp.route("/loss-damage", methods=["GET", "POST"])
@login_required
def loss_damage():
    if request.method == "POST":
        asset_id = request.form.get("asset_id")
        incident_date = _parse_date(request.form.get("reported_on")) or datetime.utcnow().date()
        incident_type = (request.form.get("type") or "loss").strip()
        description = (request.form.get("description") or "").strip()
        estimated_cost = request.form.get("estimated_cost") or None

        if estimated_cost:
            try:
                estimated_cost = float(estimated_cost)
            except ValueError:
                estimated_cost = None

        if not asset_id:
            flash("Please select an asset.", "danger")
            return redirect(url_for("assets.loss_damage"))

        asset = Asset.query.get_or_404(int(asset_id))

        record = AssetLossDamage(
            asset_id=asset.id,
            allocation_id=None,
            employee_id=current_user.id,
            incident_date=incident_date,
            incident_type=incident_type,
            description=description,
            estimated_amount=estimated_cost,
            status="pending",
            created_by_id=current_user.id,
        )
        db.session.add(record)
        db.session.commit()
        flash("Loss / Damage report submitted.", "success")
        return redirect(url_for("assets.loss_damage"))

    # GET: list
    q = (request.args.get("q") or "").strip()
    status = (request.args.get("status") or "").strip()

    query = (
        AssetLossDamage.query
        .join(Asset, AssetLossDamage.asset_id == Asset.id)
        .outerjoin(Employee, AssetLossDamage.employee_id == Employee.id)
    )

    if q:
        like = f"%{q}%"
        query = query.filter(or_(
            Asset.asset_tag.ilike(like),
            Asset.name.ilike(like),
            Employee.full_name.ilike(like),
        ))

    if status:
        query = query.filter(AssetLossDamage.status == status)

    records = query.order_by(AssetLossDamage.created_at.desc()).all()
    assets = Asset.query.order_by(Asset.asset_tag.asc()).all()

    return render_template(
        "assets/loss_damage.html",
        records=records,
        assets=assets,
        q=q,
        status=status,
    )


# ---------- Category Master ----------

@assets_bp.route("/categories", methods=["GET", "POST"])
@login_required
def categories():
    if request.method == "POST":
        mode = request.form.get("mode")
        cat_id = request.form.get("category_id")
        name = (request.form.get("name") or "").strip()
        description = (request.form.get("description") or "").strip()

        if not name:
            flash("Category name is required.", "danger")
            return redirect(url_for("assets.categories"))

        try:
            if mode == "edit" and cat_id:
                cat = AssetCategory.query.get_or_404(int(cat_id))
            else:
                cat = AssetCategory()
                db.session.add(cat)

            cat.name = name
            cat.description = description
            db.session.commit()
            flash("Category saved.", "success")
        except Exception as e:
            db.session.rollback()
            flash(f"Error saving category: {e}", "danger")

        return redirect(url_for("assets.categories"))

    q = (request.args.get("q") or "").strip()
    query = AssetCategory.query
    if q:
        like = f"%{q}%"
        query = query.filter(AssetCategory.name.ilike(like))

    categories = query.order_by(AssetCategory.name.asc()).all()
    return render_template("assets/categories.html", categories=categories, q=q)


@assets_bp.post("/categories/<int:cat_id>/delete")
@login_required
def delete_category(cat_id):
    cat = AssetCategory.query.get_or_404(cat_id)
    try:
        db.session.delete(cat)
        db.session.commit()
        flash("Category deleted.", "success")
    except Exception as e:
        db.session.rollback()
        flash(f"Cannot delete category: {e}", "danger")
    return redirect(url_for("assets.categories"))


# ---------- Vendor Master ----------

@assets_bp.route("/vendors", methods=["GET", "POST"])
@login_required
def vendors():
    if request.method == "POST":
        mode = request.form.get("mode")
        vendor_id = request.form.get("vendor_id")
        name = (request.form.get("name") or "").strip()
        contact_person = (request.form.get("contact_person") or "").strip()
        phone = (request.form.get("phone") or "").strip()
        email = (request.form.get("email") or "").strip()
        address = (request.form.get("address") or "").strip()

        if not name:
            flash("Vendor name is required.", "danger")
            return redirect(url_for("assets.vendors"))

        try:
            if mode == "edit" and vendor_id:
                v = AssetVendor.query.get_or_404(int(vendor_id))
            else:
                v = AssetVendor()
                db.session.add(v)

            v.name = name
            v.contact_person = contact_person
            v.phone = phone
            v.email = email
            v.address = address

            db.session.commit()
            flash("Vendor saved.", "success")
        except Exception as e:
            db.session.rollback()
            flash(f"Error saving vendor: {e}", "danger")

        return redirect(url_for("assets.vendors"))

    q = (request.args.get("q") or "").strip()
    query = AssetVendor.query
    if q:
        like = f"%{q}%"
        query = query.filter(AssetVendor.name.ilike(like))

    vendors = query.order_by(AssetVendor.name.asc()).all()
    return render_template("assets/vendors.html", vendors=vendors, q=q)


@assets_bp.post("/vendors/<int:vendor_id>/delete")
@login_required
def delete_vendor(vendor_id):
    v = AssetVendor.query.get_or_404(vendor_id)
    try:
        db.session.delete(v)
        db.session.commit()
        flash("Vendor deleted.", "success")
    except Exception as e:
        db.session.rollback()
        flash(f"Cannot delete vendor: {e}", "danger")
    return redirect(url_for("assets.vendors"))


# ---------- Return / Loss-Damage Approvals ----------

@assets_bp.route("/approvals")
@login_required
def approvals():
    # Pending return requests
    pending_returns = (
        AssetAllocation.query
        .join(Asset, AssetAllocation.asset_id == Asset.id)
        .join(Employee, AssetAllocation.employee_id == Employee.id)
        .filter(AssetAllocation.status == "return_pending")
        .order_by(AssetAllocation.actual_return_date.desc())
        .all()
    )

    # Pending loss / damage
    pending_ld = (
        AssetLossDamage.query
        .join(Asset, AssetLossDamage.asset_id == Asset.id)
        .join(Employee, AssetLossDamage.employee_id == Employee.id)
        .filter(AssetLossDamage.status == "pending")
        .order_by(AssetLossDamage.incident_date.desc())
        .all()
    )

    return render_template(
        "assets/approvals.html",
        pending_returns=pending_returns,
        pending_ld=pending_ld,
    )


@assets_bp.post("/approvals/return/<int:allocation_id>/<string:action>")
@login_required
def approve_return(allocation_id, action):
    alloc = AssetAllocation.query.get_or_404(allocation_id)
    asset = alloc.asset

    if alloc.status != "return_pending":
        flash("This allocation is not pending for return.", "warning")
        return redirect(url_for("assets.approvals"))

    try:
        if action == "approve":
            alloc.status = "returned"
            asset.status = "in_stock"
            flash(f"Return approved for asset {asset.asset_tag}.", "success")
        else:
            alloc.status = "allocated"
            alloc.actual_return_date = None
            alloc.condition_on_return = None
            flash(f"Return request rejected for asset {asset.asset_tag}.", "info")

        db.session.commit()
    except Exception as e:
        db.session.rollback()
        current_app.logger.exception("Error processing return approval")
        flash(f"Error processing return approval: {e}", "danger")

    return redirect(url_for("assets.approvals"))


@assets_bp.post("/approvals/loss-damage/<int:record_id>/<string:action>")
@login_required
def approve_loss_damage(record_id, action):
    rec = AssetLossDamage.query.get_or_404(record_id)
    asset = rec.asset

    if rec.status != "pending":
        flash("This loss/damage record is not pending.", "warning")
        return redirect(url_for("assets.approvals"))

    try:
        if action == "approve":
            rec.status = "approved"
            rec.approved_by_id = current_user.id
            rec.approved_at = datetime.utcnow()

            if rec.incident_type == "loss":
                asset.status = "lost"
            else:
                asset.status = "damaged"

            flash(
                f"{rec.incident_type.title()} record approved for asset {asset.asset_tag}.",
                "success",
            )
        else:
            rec.status = "rejected"
            rec.approved_by_id = current_user.id
            rec.approved_at = datetime.utcnow()
            flash(
                f"{rec.incident_type.title()} record rejected for asset {asset.asset_tag}.",
                "info",
            )

        db.session.commit()
    except Exception as e:
        db.session.rollback()
        current_app.logger.exception("Error processing loss/damage approval")
        flash(f"Error processing loss/damage approval: {e}", "danger")

    return redirect(url_for("assets.approvals"))

@assets_bp.route("/dashboard")
@login_required
def asset_dashboard():
    # Basic status counts
    total_assets = db.session.query(func.count(Asset.id)).scalar() or 0

    status_counts = dict(
        db.session.query(Asset.status, func.count(Asset.id))
        .group_by(Asset.status)
        .all()
    )

    def sc(key):
        return int(status_counts.get(key, 0) or 0)

    in_stock = sc("in_stock")
    allocated = sc("allocated")
    under_repair = sc("under_repair")
    retired = sc("retired")
    lost = sc("lost")
    damaged = sc("damaged")

    # Pending queues
    pending_returns = (
        db.session.query(func.count(AssetAllocation.id))
        .filter(AssetAllocation.status == "return_pending")
        .scalar()
        or 0
    )

    pending_loss_damage = (
        db.session.query(func.count(AssetLossDamage.id))
        .filter(AssetLossDamage.status == "pending")
        .scalar()
        or 0
    )

    # Recent allocations
    recent_allocations = (
        AssetAllocation.query
        .join(Asset, AssetAllocation.asset_id == Asset.id)
        .join(Employee, AssetAllocation.employee_id == Employee.id)
        .order_by(AssetAllocation.allocated_on.desc())
        .limit(5)
        .all()
    )

    # Recent loss/damage incidents
    recent_incidents = (
        AssetLossDamage.query
        .join(Asset, AssetLossDamage.asset_id == Asset.id)
        .outerjoin(Employee, AssetLossDamage.employee_id == Employee.id)
        .order_by(AssetLossDamage.incident_date.desc())
        .limit(5)
        .all()
    )

    return render_template(
        "assets/dashboard.html",
        total_assets=total_assets,
        in_stock=in_stock,
        allocated=allocated,
        under_repair=under_repair,
        retired=retired,
        lost=lost,
        damaged=damaged,
        pending_returns=pending_returns,
        pending_loss_damage=pending_loss_damage,
        recent_allocations=recent_allocations,
        recent_incidents=recent_incidents,
    )

@assets_bp.route("/my-assets")
@login_required
def my_assets():
    """
    Show assets allocated to the logged-in employee.
    """
    my_allocations = (
        AssetAllocation.query
        .join(Asset, AssetAllocation.asset_id == Asset.id)
        .filter(AssetAllocation.employee_id == current_user.id)
        .order_by(AssetAllocation.allocated_on.desc())
        .all()
    )

    return render_template(
        "assets/my_assets.html",
        allocations=my_allocations,
        today=date.today(),
    )