from datetime import datetime
from flask import request
from flask_jwt_extended import jwt_required, get_jwt_identity
from app.api import api_bp
from app.extensions import db
from app.models.master import Cashier, SavingsPlanType, CharityType, SavingsPlanMember
from app.models.user import User
from app.models.transactions import Credit, Debit, Loan, LoanRequest, CharityRequest, ReleaseRequest
from app.utils.auth import role_required
from app.utils.response import ok, fail


def _membership_has_related(member_id: str) -> bool:
    return any(
        [
            db.session.query(Credit.id).filter(Credit.member_id == member_id).first() is not None,
            db.session.query(Debit.id).filter(Debit.member_id == member_id).first() is not None,
            db.session.query(Loan.id).filter(Loan.member_id == member_id).first() is not None,
            db.session.query(LoanRequest.id).filter(LoanRequest.member_id == member_id).first() is not None,
            db.session.query(CharityRequest.id).filter(CharityRequest.member_id == member_id).first() is not None,
            db.session.query(ReleaseRequest.id).filter(ReleaseRequest.member_id == member_id).first() is not None,
        ]
    )


@api_bp.get("/cashiers")
@role_required("administrator", "editor")
def list_cashiers():
    cashiers = Cashier.query.order_by(Cashier.created_at.desc()).all()
    data = [
        {
            "id": c.id,
            "name": c.name,
            "status": c.status,
            "user_id": c.user_id,
            "created_at": c.created_at.isoformat(),
        }
        for c in cashiers
    ]
    return ok(data)


@api_bp.post("/cashiers")
@role_required("administrator")
def create_cashier():
    data = request.json or {}
    if not data.get("name"):
        return fail("Cashier name is required", 400)
    cashier = Cashier(
        name=data["name"],
        status=data.get("status", "active"),
        user_id=data.get("user_id"),
        created_at=datetime.utcnow(),
    )
    db.session.add(cashier)
    db.session.commit()
    return ok({"id": cashier.id}, "Cashier created", 201)


@api_bp.put("/cashiers/<int:cashier_id>")
@role_required("administrator")
def update_cashier(cashier_id):
    cashier = Cashier.query.get_or_404(cashier_id)
    data = request.json or {}
    cashier.name = data.get("name", cashier.name)
    cashier.status = data.get("status", cashier.status)
    cashier.user_id = data.get("user_id", cashier.user_id)
    db.session.commit()
    return ok({"id": cashier.id}, "Cashier updated")


@api_bp.delete("/cashiers/<int:cashier_id>")
@role_required("administrator")
def delete_cashier(cashier_id):
    cashier = Cashier.query.get_or_404(cashier_id)
    has_refs = any(
        [
            db.session.query(SavingsPlanMember.id).filter(SavingsPlanMember.cashier_id == cashier.id).first()
            is not None,
            db.session.query(Credit.id).filter(Credit.cashier_id == cashier.id).first() is not None,
            db.session.query(Debit.id).filter(Debit.cashier_id == cashier.id).first() is not None,
            db.session.query(ReleaseRequest.id).filter(ReleaseRequest.cashier_id == cashier.id).first() is not None,
        ]
    )
    if has_refs:
        return fail("Cannot delete cashier with related records", 409)
    db.session.delete(cashier)
    db.session.commit()
    return ok(message="Cashier deleted")


@api_bp.get("/savings-plan-types")
@role_required("administrator", "editor")
def list_savings_plan_types():
    plans = SavingsPlanType.query.order_by(SavingsPlanType.created_at.desc()).all()
    data = [
        {
            "id": p.id,
            "name": p.name,
            "min_period": p.min_period,
            "monthly_saving_amount": p.monthly_saving_amount,
            "charity_amount": p.charity_amount,
            "created_at": p.created_at.isoformat(),
        }
        for p in plans
    ]
    return ok(data)


@api_bp.post("/savings-plan-types")
@role_required("administrator")
def create_savings_plan_type():
    data = request.json or {}
    if not data.get("name"):
        return fail("Plan name is required", 400)
    plan = SavingsPlanType(
        name=data["name"],
        min_period=int(data.get("min_period") or 0),
        monthly_saving_amount=float(data["monthly_saving_amount"]),
        charity_amount=float(data.get("charity_amount") or 0),
    )
    db.session.add(plan)
    db.session.commit()
    return ok({"id": plan.id}, "Savings plan created", 201)


@api_bp.put("/savings-plan-types/<int:plan_id>")
@role_required("administrator")
def update_savings_plan_type(plan_id):
    plan = SavingsPlanType.query.get_or_404(plan_id)
    data = request.json or {}
    plan.name = data.get("name", plan.name)
    if "min_period" in data:
        plan.min_period = int(data["min_period"])
    if "monthly_saving_amount" in data:
        plan.monthly_saving_amount = float(data["monthly_saving_amount"])
    if "charity_amount" in data:
        plan.charity_amount = float(data["charity_amount"])
    db.session.commit()
    return ok({"id": plan.id}, "Savings plan updated")


@api_bp.delete("/savings-plan-types/<int:plan_id>")
@role_required("administrator")
def delete_savings_plan_type(plan_id):
    plan = SavingsPlanType.query.get_or_404(plan_id)
    if db.session.query(SavingsPlanMember.id).filter(SavingsPlanMember.plan_type_id == plan.id).first() is not None:
        return fail("Cannot delete plan type that is used by memberships", 409)
    db.session.delete(plan)
    db.session.commit()
    return ok(message="Savings plan deleted")


@api_bp.get("/charity-types")
@role_required("administrator", "editor")
def list_charity_types():
    types = CharityType.query.order_by(CharityType.created_at.desc()).all()
    data = [
        {
            "id": c.id,
            "charity_type": c.charity_type,
            "status": c.status,
            "created_at": c.created_at.isoformat(),
        }
        for c in types
    ]
    return ok(data)


@api_bp.post("/charity-types")
@role_required("administrator")
def create_charity_type():
    data = request.json or {}
    if not data.get("charity_type"):
        return fail("Charity type is required", 400)
    charity_type = CharityType(
        charity_type=data["charity_type"],
        status=data.get("status", "active"),
        created_at=datetime.utcnow(),
    )
    db.session.add(charity_type)
    db.session.commit()
    return ok({"id": charity_type.id}, "Charity type created", 201)


@api_bp.put("/charity-types/<int:type_id>")
@role_required("administrator")
def update_charity_type(type_id):
    charity_type = CharityType.query.get_or_404(type_id)
    data = request.json or {}
    charity_type.charity_type = data.get("charity_type", charity_type.charity_type)
    charity_type.status = data.get("status", charity_type.status)
    db.session.commit()
    return ok({"id": charity_type.id}, "Charity type updated")


@api_bp.delete("/charity-types/<int:type_id>")
@role_required("administrator")
def delete_charity_type(type_id):
    charity_type = CharityType.query.get_or_404(type_id)
    if db.session.query(CharityRequest.id).filter(CharityRequest.charity_type_id == charity_type.id).first() is not None:
        return fail("Cannot delete charity type that is used by requests", 409)
    db.session.delete(charity_type)
    db.session.commit()
    return ok(message="Charity type deleted")


@api_bp.get("/memberships")
@role_required("administrator", "editor")
def list_memberships():
    memberships = SavingsPlanMember.query.order_by(SavingsPlanMember.created_at.desc()).all()
    data = [
        {
            "id": m.id,
            "member_id": m.member_id,
            "member_short_name": m.member_short_name,
            "user_id": m.user_id,
            "plan_type_id": m.plan_type_id,
            "cashier_id": m.cashier_id,
            "effective_date": m.effective_date.isoformat() if m.effective_date else None,
            "end_date": m.end_date.isoformat() if m.end_date else None,
            "status": m.status,
        }
        for m in memberships
    ]
    return ok(data)


@api_bp.get("/memberships/my")
@jwt_required()
def list_my_memberships():
    user_id = get_jwt_identity()
    memberships = SavingsPlanMember.query.filter_by(user_id=user_id).order_by(SavingsPlanMember.created_at.desc()).all()
    data = [
        {
            "id": m.id,
            "member_id": m.member_id,
            "member_short_name": m.member_short_name,
            "user_id": m.user_id,
            "plan_type_id": m.plan_type_id,
            "cashier_id": m.cashier_id,
            "effective_date": m.effective_date.isoformat() if m.effective_date else None,
            "end_date": m.end_date.isoformat() if m.end_date else None,
            "status": m.status,
        }
        for m in memberships
    ]
    return ok(data)


@api_bp.get("/memberships/selector")
@jwt_required()
def list_memberships_for_selector():
    user = User.query.get(get_jwt_identity())
    if not user:
        return ok([])

    query = SavingsPlanMember.query
    if user.access_level != "administrator":
        query = query.filter(SavingsPlanMember.user_id == user.id)

    memberships = query.order_by(SavingsPlanMember.created_at.desc()).all()
    data = [
        {
            "id": m.id,
            "member_id": m.member_id,
            "member_short_name": m.member_short_name,
            "user_id": m.user_id,
            "user_full_name": m.user.full_name if m.user else None,
            "status": m.status,
            "cashier_id": m.cashier_id,
            "cashier_name": m.cashier.name if m.cashier else None,
            "plan_type_id": m.plan_type_id,
            "plan_type_name": m.plan_type.name if m.plan_type else None,
        }
        for m in memberships
    ]
    return ok(data)


@api_bp.get("/memberships/active")
@jwt_required()
def list_active_memberships():
    user_id = get_jwt_identity()
    user = User.query.get(user_id)
    if not user:
        return ok([])

    query = SavingsPlanMember.query.filter_by(status="active")

    if user.access_level == "member":
        query = query.filter(SavingsPlanMember.user_id == user.id)
    elif user.access_level == "editor":
        cashier_ids = [c.id for c in Cashier.query.filter_by(user_id=user.id).all()]
        if cashier_ids:
            query = query.filter(SavingsPlanMember.cashier_id.in_(cashier_ids))
        else:
            return ok([])

    members = query.order_by(SavingsPlanMember.created_at.desc()).all()
    data = [
        {
            "id": m.id,
            "member_id": m.member_id,
            "member_short_name": m.member_short_name,
            "user_id": m.user_id,
            "plan_type_id": m.plan_type_id,
            "plan_type_name": m.plan_type.name if m.plan_type else None,
            "monthly_saving_amount": m.plan_type.monthly_saving_amount if m.plan_type else None,
            "charity_amount": m.plan_type.charity_amount if m.plan_type else None,
            "cashier_id": m.cashier_id,
            "effective_date": m.effective_date.isoformat() if m.effective_date else None,
            "end_date": m.end_date.isoformat() if m.end_date else None,
            "status": m.status,
        }
        for m in members
    ]
    return ok(data)


@api_bp.get("/memberships/active-self")
@jwt_required()
def list_active_memberships_self():
    user = User.query.get(get_jwt_identity())
    if not user:
        return ok([])

    members = (
        SavingsPlanMember.query.filter_by(user_id=user.id, status="active")
        .order_by(SavingsPlanMember.created_at.desc())
        .all()
    )
    data = [
        {
            "id": m.id,
            "member_id": m.member_id,
            "member_short_name": m.member_short_name,
            "user_id": m.user_id,
            "plan_type_id": m.plan_type_id,
            "plan_type_name": m.plan_type.name if m.plan_type else None,
            "cashier_id": m.cashier_id,
            "cashier_name": m.cashier.name if m.cashier else None,
            "effective_date": m.effective_date.isoformat() if m.effective_date else None,
            "end_date": m.end_date.isoformat() if m.end_date else None,
            "status": m.status,
        }
        for m in members
    ]
    return ok(data)


@api_bp.post("/memberships")
@role_required("administrator")
def create_membership():
    data = request.json or {}
    user = User.query.get(data.get("user_id"))
    if not user:
        return fail("User not found", 404)
    if user.account_status != "Approved":
        return fail("User must be approved before assigning membership", 400)
    if data.get("status", "active") == "active":
        existing_active = SavingsPlanMember.query.filter_by(user_id=user.id, status="active").first()
        if existing_active:
            return fail("User already has an active membership", 409)
    membership = SavingsPlanMember(
        member_id=data["member_id"],
        member_short_name=data["member_short_name"],
        user_id=data["user_id"],
        plan_type_id=data["plan_type_id"],
        cashier_id=data["cashier_id"],
        effective_date=datetime.strptime(data["effective_date"], "%Y-%m-%d").date(),
        end_date=datetime.strptime(data["end_date"], "%Y-%m-%d").date() if data.get("end_date") else None,
        status=data.get("status", "active"),
    )
    db.session.add(membership)
    db.session.commit()
    return ok({"id": membership.id}, "Membership created", 201)


@api_bp.put("/memberships/<int:membership_id>")
@role_required("administrator")
def update_membership(membership_id):
    membership = SavingsPlanMember.query.get_or_404(membership_id)
    data = request.json or {}
    if data.get("user_id") and data["user_id"] != membership.user_id:
        user = User.query.get(data["user_id"])
        if not user:
            return fail("User not found", 404)
        if user.account_status != "Approved":
            return fail("User must be approved before assigning membership", 400)
        if data.get("status", membership.status) == "active":
            existing_active = SavingsPlanMember.query.filter_by(user_id=user.id, status="active").first()
            if existing_active and existing_active.id != membership.id:
                return fail("User already has an active membership", 409)
        membership.user_id = data["user_id"]

    membership.member_short_name = data.get("member_short_name", membership.member_short_name)
    membership.plan_type_id = data.get("plan_type_id", membership.plan_type_id)
    membership.cashier_id = data.get("cashier_id", membership.cashier_id)
    if data.get("effective_date"):
        membership.effective_date = datetime.strptime(data["effective_date"], "%Y-%m-%d").date()
    if data.get("end_date"):
        membership.end_date = datetime.strptime(data["end_date"], "%Y-%m-%d").date()
    if data.get("status") and data["status"] == "active":
        existing_active = SavingsPlanMember.query.filter_by(user_id=membership.user_id, status="active").first()
        if existing_active and existing_active.id != membership.id:
            return fail("User already has an active membership", 409)
    membership.status = data.get("status", membership.status)
    db.session.commit()
    return ok({"id": membership.id}, "Membership updated")


@api_bp.delete("/memberships/<int:membership_id>")
@role_required("administrator")
def delete_membership(membership_id):
    membership = SavingsPlanMember.query.get_or_404(membership_id)
    if membership.status == "active":
        return fail("Cannot delete an active membership. Set it to inactive first.", 409)
    if _membership_has_related(membership.member_id):
        return fail("Cannot delete membership with related records", 409)
    db.session.delete(membership)
    db.session.commit()
    return ok(message="Membership deleted")
