"""
IFRS + Dimensional Combined Reports Service

Integrates IFRS reporting tags with dimensional analysis to provide
multi-dimensional GL reports with full IFRS classification tracking.

This service bridges IFRS compliance and dimensional accounting by:
- Grouping transactions by both IFRS category and dimension
- Validating that all posted entries carry both IFRS and dimension tags
- Generating combined reports for regulatory and analytical purposes
"""

from typing import Dict, List, Optional, Any, Tuple
from decimal import Decimal
from datetime import date, datetime
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func

from app.models.accounting import JournalEntry, AccountingCode, AccountingEntry
from app.models.accounting_dimensions import (
    AccountingDimensionAssignment, AccountingDimension, AccountingDimensionValue
)
from app.utils.logger import get_logger

logger = get_logger(__name__)


class IFRSDimensionalReports:
    """Combined IFRS + Dimensional reporting"""

    def __init__(self, db: Session):
        self.db = db

    def get_combined_gl_with_dimensions(
        self,
        start_date: date,
        end_date: date,
        ifrs_tags: Optional[List[str]] = None,
        dimension_ids: Optional[List[str]] = None,
        branch_id: Optional[str] = None,
        group_by_ifrs: bool = True,
        group_by_dimension: bool = True
    ) -> Dict[str, Any]:
        """
        Get General Ledger with IFRS tags and dimensional breakdowns combined.

        Args:
            start_date: Report start date
            end_date: Report end date
            ifrs_tags: Filter by specific IFRS tags (e.g., ['A1.1', 'A1.2'])
            dimension_ids: Filter by specific dimensions
            branch_id: Filter by branch
            group_by_ifrs: Group results by IFRS category
            group_by_dimension: Group results by dimension value

        Returns:
            Dict with combined GL report grouped by IFRS + Dimension
        """
        # Base query: journal entries with dimension assignments and IFRS tags
        query = (
            self.db.query(
                JournalEntry.id.label('je_id'),
                JournalEntry.date,
                JournalEntry.reference,
                JournalEntry.description,
                AccountingCode.code,
                AccountingCode.name.label('account_name'),
                AccountingCode.reporting_tag.label('ifrs_tag'),
                JournalEntry.debit_amount,
                JournalEntry.credit_amount,
                AccountingDimensionAssignment.ifrs_reporting_tag,
                AccountingDimensionAssignment.ifrs_category,
                AccountingDimension.name.label('dimension_name'),
                AccountingDimensionValue.name.label('dimension_value'),
                AccountingDimensionAssignment.allocation_percentage,
            )
            .join(AccountingCode, JournalEntry.accounting_code_id == AccountingCode.id)
            .outerjoin(
                AccountingDimensionAssignment,
                JournalEntry.id == AccountingDimensionAssignment.journal_entry_id
            )
            .outerjoin(
                AccountingDimension,
                AccountingDimensionAssignment.dimension_id == AccountingDimension.id
            )
            .outerjoin(
                AccountingDimensionValue,
                AccountingDimensionAssignment.dimension_value_id == AccountingDimensionValue.id
            )
            .filter(
                and_(
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            query = query.filter(JournalEntry.branch_id == branch_id)

        if ifrs_tags:
            query = query.filter(AccountingCode.reporting_tag.in_(ifrs_tags))

        if dimension_ids:
            query = query.filter(AccountingDimension.id.in_(dimension_ids))

        entries = query.all()

        # Build result structure
        result = {
            'period': f"{start_date} to {end_date}",
            'ifrs_grouping': group_by_ifrs,
            'dimension_grouping': group_by_dimension,
            'entries_count': len(entries),
            'totals': {
                'debits': Decimal('0'),
                'credits': Decimal('0')
            },
            'data': {}
        }

        # Organize by IFRS + Dimension
        for entry in entries:
            ifrs_key = entry.ifrs_tag or 'UNTAGGED'
            dim_key = f"{entry.dimension_name}:{entry.dimension_value}" if entry.dimension_name else 'NO_DIMENSION'

            if group_by_ifrs and group_by_dimension:
                if ifrs_key not in result['data']:
                    result['data'][ifrs_key] = {
                        'ifrs_category': entry.ifrs_category or 'Unknown',
                        'dimensions': {}
                    }
                if dim_key not in result['data'][ifrs_key]['dimensions']:
                    result['data'][ifrs_key]['dimensions'][dim_key] = {
                        'dimension_name': entry.dimension_name,
                        'dimension_value': entry.dimension_value,
                        'entries': [],
                        'total_debits': Decimal('0'),
                        'total_credits': Decimal('0')
                    }

                entry_record = {
                    'date': entry.date.isoformat() if entry.date else None,
                    'account_code': entry.code,
                    'account_name': entry.account_name,
                    'reference': entry.reference,
                    'description': entry.description,
                    'debit': float(entry.debit_amount or 0),
                    'credit': float(entry.credit_amount or 0),
                    'allocation_pct': float(entry.allocation_percentage) if entry.allocation_percentage else 100.0,
                }
                result['data'][ifrs_key]['dimensions'][dim_key]['entries'].append(entry_record)
                result['data'][ifrs_key]['dimensions'][dim_key]['total_debits'] += (entry.debit_amount or 0)
                result['data'][ifrs_key]['dimensions'][dim_key]['total_credits'] += (entry.credit_amount or 0)

            result['totals']['debits'] += (entry.debit_amount or 0)
            result['totals']['credits'] += (entry.credit_amount or 0)

        # Convert Decimals to floats in result
        result['totals']['debits'] = float(result['totals']['debits'])
        result['totals']['credits'] = float(result['totals']['credits'])

        for ifrs_key in result['data']:
            for dim_key in result['data'][ifrs_key].get('dimensions', {}):
                result['data'][ifrs_key]['dimensions'][dim_key]['total_debits'] = \
                    float(result['data'][ifrs_key]['dimensions'][dim_key]['total_debits'])
                result['data'][ifrs_key]['dimensions'][dim_key]['total_credits'] = \
                    float(result['data'][ifrs_key]['dimensions'][dim_key]['total_credits'])

        return result

    def get_dimension_with_ifrs_analysis(
        self,
        dimension_id: str,
        start_date: date,
        end_date: date,
        branch_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Get all transactions for a dimension, broken down by IFRS category.

        Shows how a single dimension (e.g., Department:Sales) splits across
        different IFRS categories (Assets, Liabilities, Revenue, Expenses, etc.)
        """
        query = (
            self.db.query(
                AccountingDimensionValue.name.label('dimension_value'),
                AccountingCode.reporting_tag.label('ifrs_tag'),
                func.sum(JournalEntry.debit_amount).label('total_debits'),
                func.sum(JournalEntry.credit_amount).label('total_credits'),
                func.count(JournalEntry.id).label('entry_count'),
            )
            .join(
                AccountingDimensionAssignment,
                JournalEntry.id == AccountingDimensionAssignment.journal_entry_id
            )
            .join(
                AccountingDimensionValue,
                AccountingDimensionAssignment.dimension_value_id == AccountingDimensionValue.id
            )
            .join(
                AccountingCode,
                JournalEntry.accounting_code_id == AccountingCode.id
            )
            .filter(
                and_(
                    AccountingDimensionValue.dimension_id == dimension_id,
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            query = query.filter(JournalEntry.branch_id == branch_id)

        query = query.group_by(
            AccountingDimensionValue.name,
            AccountingCode.reporting_tag
        )

        results = query.all()

        ifrs_breakdown = {}
        for result in results:
            dim_val = result.dimension_value or 'Unspecified'
            ifrs_tag = result.ifrs_tag or 'UNTAGGED'

            if dim_val not in ifrs_breakdown:
                ifrs_breakdown[dim_val] = {}

            ifrs_breakdown[dim_val][ifrs_tag] = {
                'debits': float(result.total_debits or 0),
                'credits': float(result.total_credits or 0),
                'entry_count': result.entry_count,
                'net': float((result.total_debits or 0) - (result.total_credits or 0))
            }

        return {
            'dimension_id': dimension_id,
            'period': f"{start_date} to {end_date}",
            'data': ifrs_breakdown
        }

    def get_ifrs_category_with_dimensions(
        self,
        ifrs_tag: str,
        start_date: date,
        end_date: date,
        branch_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Get all transactions in an IFRS category, broken down by dimensions.

        Shows how a single IFRS category (e.g., Current Assets) distributes
        across different dimensions (Departments, Projects, etc.)
        """
        query = (
            self.db.query(
                AccountingDimension.name.label('dimension_name'),
                AccountingDimensionValue.name.label('dimension_value'),
                func.sum(JournalEntry.debit_amount).label('total_debits'),
                func.sum(JournalEntry.credit_amount).label('total_credits'),
                func.count(JournalEntry.id).label('entry_count'),
            )
            .join(
                AccountingCode,
                JournalEntry.accounting_code_id == AccountingCode.id
            )
            .outerjoin(
                AccountingDimensionAssignment,
                JournalEntry.id == AccountingDimensionAssignment.journal_entry_id
            )
            .outerjoin(
                AccountingDimension,
                AccountingDimensionAssignment.dimension_id == AccountingDimension.id
            )
            .outerjoin(
                AccountingDimensionValue,
                AccountingDimensionAssignment.dimension_value_id == AccountingDimensionValue.id
            )
            .filter(
                and_(
                    AccountingCode.reporting_tag == ifrs_tag,
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            query = query.filter(JournalEntry.branch_id == branch_id)

        query = query.group_by(
            AccountingDimension.name,
            AccountingDimensionValue.name
        )

        results = query.all()

        dimension_breakdown = {}
        for result in results:
            dim_name = result.dimension_name or 'NO_DIMENSION'
            dim_val = result.dimension_value or 'Unassigned'

            if dim_name not in dimension_breakdown:
                dimension_breakdown[dim_name] = {}

            dimension_breakdown[dim_name][dim_val] = {
                'debits': float(result.total_debits or 0),
                'credits': float(result.total_credits or 0),
                'entry_count': result.entry_count,
                'net': float((result.total_debits or 0) - (result.total_credits or 0))
            }

        return {
            'ifrs_tag': ifrs_tag,
            'period': f"{start_date} to {end_date}",
            'data': dimension_breakdown
        }

    def validate_ifrs_dimension_coverage(
        self,
        start_date: date,
        end_date: date,
        branch_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Validate that all posted journal entries carry both IFRS and dimension tags.

        Returns:
        - Total entries posted
        - Entries with IFRS tags
        - Entries with dimension assignments
        - Entries with BOTH (fully tagged)
        - Entries with NEITHER (audit alert)
        """
        total_query = (
            self.db.query(func.count(JournalEntry.id))
            .filter(
                and_(
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            total_query = total_query.filter(JournalEntry.branch_id == branch_id)

        total_entries = total_query.scalar() or 0

        # Entries with IFRS tags
        ifrs_tagged = (
            self.db.query(func.count(JournalEntry.id))
            .join(AccountingCode, JournalEntry.accounting_code_id == AccountingCode.id)
            .filter(
                and_(
                    AccountingCode.reporting_tag.isnot(None),
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            ifrs_tagged = ifrs_tagged.filter(JournalEntry.branch_id == branch_id)

        ifrs_count = ifrs_tagged.scalar() or 0

        # Entries with dimensions
        dimensional = (
            self.db.query(func.count(JournalEntry.id.distinct()))
            .join(
                AccountingDimensionAssignment,
                JournalEntry.id == AccountingDimensionAssignment.journal_entry_id
            )
            .filter(
                and_(
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            dimensional = dimensional.filter(JournalEntry.branch_id == branch_id)

        dimension_count = dimensional.scalar() or 0

        # Entries with BOTH
        both_query = (
            self.db.query(func.count(JournalEntry.id.distinct()))
            .join(AccountingCode, JournalEntry.accounting_code_id == AccountingCode.id)
            .join(
                AccountingDimensionAssignment,
                JournalEntry.id == AccountingDimensionAssignment.journal_entry_id
            )
            .filter(
                and_(
                    AccountingCode.reporting_tag.isnot(None),
                    JournalEntry.date >= start_date,
                    JournalEntry.date <= end_date
                )
            )
        )

        if branch_id:
            both_query = both_query.filter(JournalEntry.branch_id == branch_id)

        both_count = both_query.scalar() or 0

        return {
            'period': f"{start_date} to {end_date}",
            'total_entries': total_entries,
            'with_ifrs_tags': {
                'count': ifrs_count,
                'percentage': (ifrs_count / total_entries * 100) if total_entries > 0 else 0
            },
            'with_dimensions': {
                'count': dimension_count,
                'percentage': (dimension_count / total_entries * 100) if total_entries > 0 else 0
            },
            'with_both_ifrs_and_dimensions': {
                'count': both_count,
                'percentage': (both_count / total_entries * 100) if total_entries > 0 else 0
            },
            'coverage_status': 'COMPLIANT' if both_count == total_entries else 'INCOMPLETE',
            'missing_ifrs': total_entries - ifrs_count,
            'missing_dimensions': total_entries - dimension_count,
            'missing_both': total_entries - both_count
        }
