from __future__ import annotations
import uuid
from datetime import datetime, date
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.database import get_db
from app.models.finance import Expense, Income, Account, Payment
from app.services.auth import get_current_user, require_permission
from app.services.accounting import AccountingError, JournalEntry
from app.services.transaction_postings import post_payment, reverse_payment
from app.services.finance_postings import (
    post_expense, reverse_expense, post_income, reverse_income,
    generate_recurring_expenses, expense_posting_entry, income_posting_entry,
)

router = APIRouter(prefix="/finance", tags=["finance"])


class AccountCreate(BaseModel):
    name: str
    type: str
    balance: float = 0.0
    is_active: bool = True

class AccountUpdate(BaseModel):
    name: Optional[str] = None
    type: Optional[str] = None
    balance: Optional[float] = None
    is_active: Optional[bool] = None

class AccountOut(BaseModel):
    id: str
    name: str
    type: str
    balance: float
    is_active: bool
    created_at: datetime
    model_config = {"from_attributes": True}

class ExpenseCreate(BaseModel):
    category: str
    description: str
    amount: float
    date: date
    payment_method: str = "cash"
    account_id: Optional[str] = None
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None
    approval_status: str = "approved"  # pending/approved/rejected
    attachment_path: Optional[str] = None
    recurring: bool = False
    recurring_frequency: str = "monthly"  # monthly/quarterly/yearly
    recurring_until: Optional[date] = None

class ExpenseUpdate(BaseModel):
    category: Optional[str] = None
    description: Optional[str] = None
    amount: Optional[float] = None
    date: Optional[date] = None
    payment_method: Optional[str] = None
    account_id: Optional[str] = None
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None
    approval_status: Optional[str] = None
    attachment_path: Optional[str] = None
    recurring: Optional[bool] = None
    recurring_frequency: Optional[str] = None
    recurring_until: Optional[date] = None

class ExpenseOut(BaseModel):
    id: str
    expense_number: str
    category: str
    description: str
    amount: float
    date: date
    payment_method: str
    account_id: Optional[str] = None
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None
    approval_status: str = "approved"
    approved_by: Optional[str] = None
    approved_at: Optional[datetime] = None
    attachment_path: Optional[str] = None
    recurring: bool = False
    recurring_frequency: str = "monthly"
    recurring_until: Optional[date] = None
    created_by: str
    created_at: datetime
    model_config = {"from_attributes": True}

class IncomeCreate(BaseModel):
    category: str
    description: str
    amount: float
    date: date
    payment_method: str = "cash"
    account_id: Optional[str] = None
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None

class IncomeUpdate(BaseModel):
    category: Optional[str] = None
    description: Optional[str] = None
    amount: Optional[float] = None
    date: Optional[date] = None
    payment_method: Optional[str] = None
    account_id: Optional[str] = None
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None

class IncomeOut(BaseModel):
    id: str
    income_number: str
    category: str
    description: str
    amount: float
    date: date
    payment_method: str
    account_id: Optional[str] = None
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None
    created_by: str
    created_at: datetime
    model_config = {"from_attributes": True}

class PaymentAllocationCreate(BaseModel):
    document_type: str = "sale"
    document_id: str
    amount: float

class PaymentCreate(BaseModel):
    type: str
    party_type: str
    party_id: str
    amount: float
    date: date
    method: str = "cash"
    branch_id: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None

class PaymentUpdate(BaseModel):
    type: Optional[str] = None
    party_type: Optional[str] = None
    party_id: Optional[str] = None
    amount: Optional[float] = None
    date: Optional[date] = None
    method: Optional[str] = None
    reference: Optional[str] = None
    notes: Optional[str] = None

class PaymentOut(BaseModel):
    id: str
    payment_number: str
    type: str
    party_type: str
    party_id: str
    amount: float
    date: date
    method: str
    reference: Optional[str] = None
    notes: Optional[str] = None
    created_by: str
    created_at: datetime
    model_config = {"from_attributes": True}


def _generate_expense_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(Expense).filter(
        Expense.expense_number.like(f"EXP-{year}-%")
    ).order_by(Expense.expense_number.desc()).first()
    if last:
        seq = int(last.expense_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"EXP-{year}-{seq:06d}"


def _generate_income_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(Income).filter(
        Income.income_number.like(f"INC-{year}-%")
    ).order_by(Income.income_number.desc()).first()
    if last:
        seq = int(last.income_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"INC-{year}-{seq:06d}"


def _generate_payment_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(Payment).filter(
        Payment.payment_number.like(f"PAY-{year}-%")
    ).order_by(Payment.payment_number.desc()).first()
    if last:
        seq = int(last.payment_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"PAY-{year}-{seq:06d}"


@router.get("/accounts", response_model=list[AccountOut])
def list_accounts(db: Session = Depends(get_db), _=Depends(require_permission("finance", "view"))):
    return db.query(Account).order_by(Account.name).all()


@router.post("/accounts", response_model=AccountOut, status_code=201)
def create_account(data: AccountCreate, db: Session = Depends(get_db), _=Depends(require_permission("finance", "create"))):
    obj = Account(**data.model_dump())
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj


@router.get("/accounts/{account_id}", response_model=AccountOut)
def get_account(account_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "view"))):
    obj = db.query(Account).filter(Account.id == account_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Account not found")
    return obj


@router.put("/accounts/{account_id}", response_model=AccountOut)
def update_account(account_id: str, data: AccountUpdate, db: Session = Depends(get_db), _=Depends(require_permission("finance", "edit"))):
    obj = db.query(Account).filter(Account.id == account_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Account not found")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.delete("/accounts/{account_id}")
def delete_account(account_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "delete"))):
    obj = db.query(Account).filter(Account.id == account_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Account not found")
    db.delete(obj)
    db.commit()
    return {"detail": "Account deleted"}


@router.get("/expenses", response_model=list[ExpenseOut])
def list_expenses(
    category: Optional[str] = None,
    from_date: Optional[date] = None,
    to_date: Optional[date] = None,
    db: Session = Depends(get_db),
    _=Depends(require_permission("finance", "view")),
):
    q = db.query(Expense)
    if category:
        q = q.filter(Expense.category == category)
    if from_date:
        q = q.filter(Expense.date >= from_date)
    if to_date:
        q = q.filter(Expense.date <= to_date)
    return q.order_by(Expense.created_at.desc()).all()


@router.post("/expenses", response_model=ExpenseOut, status_code=201)
def create_expense(data: ExpenseCreate, db: Session = Depends(get_db), current_user=Depends(require_permission("finance", "create"))):
    obj = Expense(**data.model_dump(), expense_number=_generate_expense_number(db), created_by=current_user.id)
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj


@router.post("/expenses/recurring/generate")
def generate_expense_instances(db: Session = Depends(get_db),
                                current_user=Depends(require_permission("finance", "create"))):
    try:
        created = generate_recurring_expenses(db, current_user.id)
        db.commit()
    except AccountingError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc))
    return {"generated": len(created),
            "items": [e.id for e in created]}


@router.get("/expenses/{expense_id}/posting")
def get_expense_posting_status(expense_id: str, db: Session = Depends(get_db),
                               _=Depends(require_permission("finance", "view"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    entry = expense_posting_entry(db, obj)
    return {
        "posted": bool(entry and entry.status == "posted"),
        "status": entry.status if entry else None,
        "entry_number": entry.entry_number if entry else None,
        "approval_status": obj.approval_status,
    }


@router.post("/expenses/{expense_id}/post")
def post_expense_entry(expense_id: str, db: Session = Depends(get_db),
                       current_user=Depends(require_permission("finance", "edit"))):
    try:
        entry = post_expense(db, expense_id, current_user.id)
        db.commit()
    except AccountingError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc))
    return {"expense_id": expense_id, "posted": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.post("/expenses/{expense_id}/approve")
def approve_expense_entry(expense_id: str, db: Session = Depends(get_db),
                          current_user=Depends(require_permission("finance", "edit"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    if obj.approval_status == "rejected":
        raise HTTPException(status_code=400, detail="Expense was rejected")
    try:
        # approving also posts (auto-post for approved expenses)
        entry = post_expense(db, expense_id, current_user.id, approve=True)
        db.commit()
    except AccountingError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc))
    return {"expense_id": expense_id, "approval_status": "approved", "posted": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.post("/expenses/{expense_id}/reject")
def reject_expense_entry(expense_id: str, reason: Optional[str] = None,
                         db: Session = Depends(get_db),
                         current_user=Depends(require_permission("finance", "edit"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    entry = expense_posting_entry(db, obj)
    if obj.approval_status == "rejected":
        raise HTTPException(status_code=400, detail="Expense already rejected")
    if entry and entry.status == "posted":
        raise HTTPException(status_code=400, detail="Posted expenses cannot be rejected; reverse first")
    obj.approval_status = "rejected"
    obj.approved_by = current_user.id
    obj.approved_at = datetime.now()
    if reason:
        obj.notes = (obj.notes or "") + f" [rejected: {reason}]"
    db.commit()
    return {"expense_id": expense_id, "approval_status": "rejected"}


@router.post("/expenses/{expense_id}/reverse")
def reverse_expense_entry(expense_id: str, reason: Optional[str] = None,
                          db: Session = Depends(get_db),
                          current_user=Depends(require_permission("finance", "edit"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    try:
        entry = reverse_expense(db, expense_id, current_user.id, reason)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"expense_id": obj.id, "expense_number": obj.expense_number, "reversed": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.get("/expenses/{expense_id}", response_model=ExpenseOut)
def get_expense(expense_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "view"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    return obj


@router.put("/expenses/{expense_id}", response_model=ExpenseOut)
def update_expense(expense_id: str, data: ExpenseUpdate, db: Session = Depends(get_db), _=Depends(require_permission("finance", "edit"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.delete("/expenses/{expense_id}")
def delete_expense(expense_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "delete"))):
    obj = db.query(Expense).filter(Expense.id == expense_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Expense not found")
    db.delete(obj)
    db.commit()
    return {"detail": "Expense deleted"}


@router.get("/income", response_model=list[IncomeOut])
def list_income(
    category: Optional[str] = None,
    from_date: Optional[date] = None,
    to_date: Optional[date] = None,
    db: Session = Depends(get_db),
    _=Depends(require_permission("finance", "view")),
):
    q = db.query(Income)
    if category:
        q = q.filter(Income.category == category)
    if from_date:
        q = q.filter(Income.date >= from_date)
    if to_date:
        q = q.filter(Income.date <= to_date)
    return q.order_by(Income.created_at.desc()).all()


@router.post("/income", response_model=IncomeOut, status_code=201)
def create_income(data: IncomeCreate, db: Session = Depends(get_db), current_user=Depends(require_permission("finance", "create"))):
    obj = Income(**data.model_dump(), income_number=_generate_income_number(db), created_by=current_user.id)
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj


@router.get("/income/{income_id}/posting")
def get_income_posting_status(income_id: str, db: Session = Depends(get_db),
                              _=Depends(require_permission("finance", "view"))):
    obj = db.query(Income).filter(Income.id == income_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Income not found")
    entry = income_posting_entry(db, obj)
    return {
        "posted": bool(entry and entry.status == "posted"),
        "status": entry.status if entry else None,
        "entry_number": entry.entry_number if entry else None,
    }


@router.post("/income/{income_id}/post")
def post_income_entry(income_id: str, db: Session = Depends(get_db),
                      current_user=Depends(require_permission("finance", "edit"))):
    try:
        entry = post_income(db, income_id, current_user.id)
        db.commit()
    except AccountingError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc))
    return {"income_id": income_id, "posted": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.post("/income/{income_id}/reverse")
def reverse_income_entry(income_id: str, reason: Optional[str] = None,
                         db: Session = Depends(get_db),
                         current_user=Depends(require_permission("finance", "edit"))):
    obj = db.query(Income).filter(Income.id == income_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Income not found")
    try:
        entry = reverse_income(db, income_id, current_user.id, reason)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"income_id": obj.id, "income_number": obj.income_number, "reversed": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.get("/income/{income_id}", response_model=IncomeOut)
def get_income(income_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "view"))):
    obj = db.query(Income).filter(Income.id == income_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Income not found")
    return obj


@router.put("/income/{income_id}", response_model=IncomeOut)
def update_income(income_id: str, data: IncomeUpdate, db: Session = Depends(get_db), _=Depends(require_permission("finance", "edit"))):
    obj = db.query(Income).filter(Income.id == income_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Income not found")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.delete("/income/{income_id}")
def delete_income(income_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "delete"))):
    obj = db.query(Income).filter(Income.id == income_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Income not found")
    db.delete(obj)
    db.commit()
    return {"detail": "Income deleted"}


@router.get("/payments", response_model=list[PaymentOut])
def list_payments(
    party_type: Optional[str] = None,
    party_id: Optional[str] = None,
    from_date: Optional[date] = None,
    to_date: Optional[date] = None,
    db: Session = Depends(get_db),
    _=Depends(require_permission("finance", "view")),
):
    q = db.query(Payment)
    if party_type:
        q = q.filter(Payment.party_type == party_type)
    if party_id:
        q = q.filter(Payment.party_id == party_id)
    if from_date:
        q = q.filter(Payment.date >= from_date)
    if to_date:
        q = q.filter(Payment.date <= to_date)
    return q.order_by(Payment.created_at.desc()).all()


@router.post("/payments", response_model=PaymentOut, status_code=201)
def create_payment(data: PaymentCreate, db: Session = Depends(get_db), current_user=Depends(require_permission("finance", "create"))):
    obj = Payment(**data.model_dump(), payment_number=_generate_payment_number(db), created_by=current_user.id)
    db.add(obj)
    db.flush()
    try:
        post_payment(db, obj.id, current_user.id)
    except AccountingError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    db.refresh(obj)
    return obj


@router.get("/payments/{payment_id}", response_model=PaymentOut)
def get_payment(payment_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "view"))):
    obj = db.query(Payment).filter(Payment.id == payment_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Payment not found")
    return obj


@router.post("/payments/{payment_id}/reverse")
def reverse_payment_entry(payment_id: str, reason: Optional[str] = None,
                          db: Session = Depends(get_db), current_user=Depends(require_permission("finance", "edit"))):
    obj = db.query(Payment).filter(Payment.id == payment_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Payment not found")
    try:
        entry = reverse_payment(db, obj.id, current_user.id, reason)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"payment_id": obj.id, "payment_number": obj.payment_number, "reversed": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.put("/payments/{payment_id}", response_model=PaymentOut)
def update_payment(payment_id: str, data: PaymentUpdate, db: Session = Depends(get_db), _=Depends(require_permission("finance", "edit"))):
    obj = db.query(Payment).filter(Payment.id == payment_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Payment not found")
    posted = db.query(JournalEntry).filter(
        JournalEntry.source == "payment",
        JournalEntry.reference_type == "payment",
        JournalEntry.reference_id == payment_id,
        JournalEntry.is_reversal == False,  # noqa: E712
    ).first()
    if posted:
        raise HTTPException(status_code=400, detail="Posted payments cannot be edited; reverse first")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.delete("/payments/{payment_id}")
def delete_payment(payment_id: str, db: Session = Depends(get_db), _=Depends(require_permission("finance", "delete"))):
    obj = db.query(Payment).filter(Payment.id == payment_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Payment not found")
    posted = db.query(JournalEntry).filter(
        JournalEntry.source == "payment",
        JournalEntry.reference_type == "payment",
        JournalEntry.reference_id == payment_id,
        JournalEntry.is_reversal == False,  # noqa: E712
    ).first()
    if posted:
        raise HTTPException(status_code=400, detail="Posted payments cannot be deleted; reverse first")
    db.delete(obj)
    db.commit()
    return {"detail": "Payment deleted"}
