from decimal import Decimal, ROUND_HALF_UP

from django.db import transaction
from django.utils import timezone

from apps.documents.models import Document, DocumentCategory
from apps.investments.models import Investment, InvestmentCertificate, InvestmentStatus
from apps.notifications.models import NotificationKind
from apps.notifications.services import notify_user
from apps.projects.models import InvestmentWindowStatus, RuleModel
from apps.transactions.models import Transaction, TransactionStatus, TransactionType


FOUR = Decimal("0.0001")


def next_investment_reference():
    year = timezone.now().year
    prefix = f"INV-{year}-"
    last = (
        Investment.objects.filter(reference__startswith=prefix)
        .exclude(reference="")
        .order_by("-reference")
        .first()
    )
    n = 1
    if last and last.reference:
        try:
            n = int(last.reference.rsplit("-", 1)[-1]) + 1
        except ValueError:
            n = Investment.objects.filter(reference__startswith=prefix).count() + 1
    return f"{prefix}{n:06d}"


def next_agreement_code(project):
    slug = (project.slug or "PRJ").upper().replace("-", "")[:8]
    year = timezone.now().year
    count = Investment.objects.filter(project=project).count() + 1
    return f"SH-{slug}-{year}-{count:03d}"


def next_certificate_number(investment):
    year = timezone.now().year
    return f"CERT-{year}-{investment.id:06d}"


def calculate_participation(terms, amount: Decimal) -> Decimal:
    amount = Decimal(amount)
    if amount <= 0:
        return Decimal("0")
    if terms.rule_model == RuleModel.UNIT and terms.unit_price and terms.total_units:
        units = amount / terms.unit_price
        share = (units / Decimal(terms.total_units)) * Decimal("100")
        return share.quantize(FOUR, rounding=ROUND_HALF_UP)
    pool = terms.investment_pool or Decimal("0")
    if pool <= 0:
        return Decimal("0")
    share = (amount / pool) * Decimal("100")
    return share.quantize(FOUR, rounding=ROUND_HALF_UP)


def next_txn_reference(prefix="TX-INV"):
    year = timezone.now().year
    head = f"{prefix}-{year}-"
    last = (
        Transaction.objects.filter(reference__startswith=head)
        .order_by("-reference")
        .first()
    )
    n = 1
    if last:
        try:
            n = int(last.reference.rsplit("-", 1)[-1]) + 1
        except ValueError:
            n = Transaction.objects.filter(reference__startswith=head).count() + 1
    return f"{head}{n:04d}"


def ensure_capital_transaction(investment, status=TransactionStatus.PENDING):
    existing = investment.transactions.filter(txn_type=TransactionType.CAPITAL).first()
    if existing:
        return existing
    return Transaction.objects.create(
        reference=next_txn_reference(),
        investor=investment.investor,
        investment=investment,
        project=investment.project,
        txn_type=TransactionType.CAPITAL,
        status=status,
        currency=investment.currency,
        amount=investment.committed_amount,
        occurred_on=timezone.now().date(),
        description=f"Capital order {investment.reference or investment.agreement_code}",
    )


def remaining_capacity(terms) -> Decimal:
    subscribed = (
        terms.project.investments.exclude(status=InvestmentStatus.CANCELLED).aggregate(
            total=models_sum()
        )["total"]
        or Decimal("0")
    )
    return Decimal(terms.investment_pool or 0) - Decimal(subscribed)


def models_sum():
    from django.db.models import Sum

    return Sum("committed_amount")


def quote_investment(project, amount: Decimal):
    terms = getattr(project, "investment_terms", None)
    if terms is None:
        raise ValueError("This project is not open for structured investment.")
    amount = Decimal(amount)
    remaining = remaining_capacity(terms)
    participation = calculate_participation(terms, amount)
    return {
        "project": project.slug,
        "project_name": project.name,
        "amount": str(amount),
        "investment_pool": str(terms.investment_pool),
        "remaining_capacity": str(remaining),
        "participation_percent": str(participation),
        "profit_share_percent": str(participation if terms.rule_model != RuleModel.CONTRACTUAL else Decimal("0")),
        "rule_model": terms.rule_model,
        "min_investment": str(terms.min_investment),
        "max_investment": str(terms.max_investment or terms.investment_pool),
        "status": terms.status,
        "disclaimer": terms.disclaimer,
    }


def validate_subscription(investor, project, amount: Decimal):
    terms = getattr(project, "investment_terms", None)
    if terms is None or not terms.is_published:
        raise ValueError("Investment terms are not published for this project.")
    if terms.status != InvestmentWindowStatus.OPEN:
        raise ValueError("This investment window is not open.")
    now = timezone.now()
    if terms.opens_at and now < terms.opens_at:
        raise ValueError("This investment window has not opened yet.")
    if terms.closes_at and now > terms.closes_at:
        raise ValueError("This investment window has closed.")
    if investor.kyc_status != "verified":
        raise ValueError("KYC verification is required before investing.")
    if investor.status != "active":
        raise ValueError("Investor account is not active.")
    amount = Decimal(amount)
    if amount < terms.min_investment:
        raise ValueError(f"Minimum investment is {terms.min_investment}.")
    ceiling = terms.max_investment or terms.investment_pool
    if ceiling and amount > ceiling:
        raise ValueError(f"Maximum investment is {ceiling}.")
    remaining = remaining_capacity(terms)
    if amount > remaining:
        raise ValueError("Amount exceeds remaining investment capacity.")
    return terms


@transaction.atomic
def create_investor_order(investor, project, amount, payment_method="", payment_reference="", accept_agreement=False):
    if not accept_agreement:
        raise ValueError("You must accept the investment agreement and disclaimer.")
    amount = Decimal(amount)
    terms = validate_subscription(investor, project, amount)
    participation = calculate_participation(terms, amount)
    profit_share = participation if terms.rule_model != RuleModel.CONTRACTUAL else Decimal("0")
    investment = Investment.objects.create(
        investor=investor,
        project=project,
        agreement_code=next_agreement_code(project),
        reference=next_investment_reference(),
        status=InvestmentStatus.PAYMENT_PENDING,
        source="investor",
        currency=terms.currency,
        committed_amount=amount,
        participation_percent=participation,
        profit_share_percent=profit_share,
        payment_method=payment_method,
        payment_reference=payment_reference,
        agreement_accepted_at=timezone.now(),
    )
    ensure_capital_transaction(investment, status=TransactionStatus.PENDING)
    notify_user(
        investor.user,
        "Investment received",
        f"Your investment {investment.reference} for {project.name} is pending payment verification.",
        kind=NotificationKind.TRANSACTION,
        project=project,
    )
    return investment


@transaction.atomic
def verify_payment(investment, user, payment_reference="", received_amount=None):
    investment.payment_verified_at = timezone.now()
    investment.payment_verified_by = user
    if payment_reference:
        investment.payment_reference = payment_reference
    if received_amount is not None:
        investment.received_amount = Decimal(received_amount)
    else:
        investment.received_amount = investment.committed_amount
    investment.status = InvestmentStatus.PENDING_VERIFICATION
    investment.save()
    txn = ensure_capital_transaction(investment, status=TransactionStatus.POSTED)
    txn.status = TransactionStatus.POSTED
    txn.amount = investment.received_amount or investment.committed_amount
    txn.posted_by = user
    txn.save(update_fields=["status", "amount", "posted_by", "updated_at"])
    notify_user(
        investment.investor.user,
        "Payment verified",
        f"Payment for {investment.reference} has been verified and is awaiting activation.",
        kind=NotificationKind.TRANSACTION,
        project=investment.project,
    )
    return investment


@transaction.atomic
def activate_investment(investment, user):
    if not investment.payment_verified_at:
        raise ValueError("Payment must be verified before activation.")
    investment.status = InvestmentStatus.ACTIVE
    investment.activated_at = timezone.now()
    if not investment.agreement_executed_on:
        investment.agreement_executed_on = timezone.now().date()
    if not investment.reference:
        investment.reference = next_investment_reference()
    investment.save()
    cert, _ = InvestmentCertificate.objects.get_or_create(
        investment=investment,
        defaults={"certificate_number": next_certificate_number(investment)},
    )
    if cert.document_id is None:
        document = Document.objects.create(
            title=f"Investment Certificate {cert.certificate_number}",
            category=DocumentCategory.OTHER,
            visibility="investor",
            meta=f"{investment.reference} · {investment.project.name}",
            investor=investment.investor,
            project=investment.project,
            investment=investment,
            uploaded_by=user,
        )
        cert.document = document
        cert.save(update_fields=["document"])
    notify_user(
        investment.investor.user,
        "Investment activated",
        f"{investment.reference} is now ACTIVE. Your participation is {investment.participation_percent}%.",
        kind=NotificationKind.TRANSACTION,
        project=investment.project,
    )
    return investment


def cap_table_for(project):
    terms = getattr(project, "investment_terms", None)
    rows = []
    subscribed = Decimal("0")
    for inv in project.investments.exclude(status=InvestmentStatus.CANCELLED).select_related("investor", "investor__user"):
        subscribed += inv.committed_amount
        rows.append(
            {
                "investor_id": inv.investor_id,
                "investor_code": inv.investor.account_code,
                "investor_email": inv.investor.user.email,
                "investment_id": inv.id,
                "reference": inv.reference or inv.agreement_code,
                "amount": str(inv.committed_amount),
                "participation_percent": str(inv.participation_percent),
                "status": inv.status,
            }
        )
    pool = Decimal(terms.investment_pool) if terms else subscribed
    company = Decimal(terms.company_contribution) if terms else Decimal("0")
    reserved = Decimal(terms.reserved_amount) if terms else Decimal("0")
    remaining = (pool - subscribed) if pool else Decimal("0")
    return {
        "project": project.slug,
        "project_name": project.name,
        "investment_pool": str(pool),
        "company_contribution": str(company),
        "reserved_amount": str(reserved),
        "subscribed": str(subscribed),
        "remaining_capacity": str(remaining),
        "rule_model": terms.rule_model if terms else None,
        "rows": rows,
    }
