# import sys  # Removed - no longer needed
import os

import oracledb
from shared.database.oracle_pool import oracle_pool
from ..models.treasury import BankCashRegister, RegisterFilter, RegisterListResponse, AccountingPeriod
from ..cache.decorators import cached
from decimal import Decimal
from typing import Optional, List, Tuple, Any
import logging

logger = logging.getLogger(__name__)

class TreasuryService:
    """Service pentru trezorerie - registru casă și bancă"""

    @staticmethod
    @cached(cache_type='schema', key_params=['company_id'])
    async def _get_schema(company_id: int) -> str:
        """Obține schema pentru company_id (CACHED PERMANENT)"""
        async with oracle_pool.get_connection() as connection:
            with connection.cursor() as cursor:
                schema_query = """
                    SELECT schema
                    FROM CONTAFIN_ORACLE.v_nom_firme
                    WHERE id_firma = :company_id
                """
                cursor.execute(schema_query, {'company_id': company_id})
                schema_result = cursor.fetchone()

                if not schema_result:
                    raise ValueError(f"Schema not found for company {company_id}")

                return schema_result[0]

    @staticmethod
    def _get_view_query(schema: str, register_type: Optional[str] = None) -> str:
        """
        Construiește query-ul pentru view-ul vbancasa corespunzător.
        Dacă register_type este None, returnează UNION ALL pentru toate tipurile.
        NU se filtrează pe incasari/plati > 0 - se afișează TOATE înregistrările!
        """
        view_configs = {
            'BANCA_LEI': {
                'view': f'{schema}.vbancasa_5121_cum',
                'incasari_col': 'incasari',
                'plati_col': 'plati',
                'valuta': "'RON'",
                'tip': "'BANCA LEI'"
            },
            'BANCA_VALUTA': {
                'view': f'{schema}.vbancasa_5124_cum',
                'incasari_col': 'incasval',
                'plati_col': 'platival',
                'valuta': "COALESCE(numeval, 'EUR')",
                'tip': "'BANCA VALUTA'"
            },
            'CASA_LEI': {
                'view': f'{schema}.vbancasa_5311_cum',
                'incasari_col': 'incasari',
                'plati_col': 'plati',
                'valuta': "'RON'",
                'tip': "'CASA LEI'"
            },
            'CASA_VALUTA': {
                'view': f'{schema}.vbancasa_5314_cum',
                'incasari_col': 'incasval',
                'plati_col': 'platival',
                'valuta': "COALESCE(numeval, 'EUR')",
                'tip': "'CASA VALUTA'"
            }
        }

        def build_select(config):
            # NU se filtrează - se afișează TOATE înregistrările
            # SOLD CUMULAT: Running balance per bancasa using window function
            # NULL-date rows (opening balance) come first due to NULLS FIRST
            return f"""
                SELECT
                    nume, nract, dataact, bancasa,
                    {config['incasari_col']} as incasari,
                    {config['plati_col']} as plati,
                    SUM({config['incasari_col']} - {config['plati_col']}) OVER (
                        PARTITION BY bancasa
                        ORDER BY dataact ASC NULLS FIRST, nract ASC NULLS FIRST
                        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
                    ) as sold,
                    {config['valuta']} as valuta,
                    {config['tip']} as tip_registru,
                    explicatia
                FROM {config['view']}
            """

        if register_type and register_type in view_configs:
            return build_select(view_configs[register_type])
        else:
            # UNION ALL pentru toate tipurile
            queries = [build_select(cfg) for cfg in view_configs.values()]
            return " UNION ALL ".join(queries)

    @staticmethod
    @cached(cache_type='treasury', key_params=['filter_params', 'username'])
    async def get_bank_cash_register(filter_params: RegisterFilter, username: str) -> RegisterListResponse:
        """
        Obține registrul de casă și bancă din vbancasa views (CACHED 10 min)

        IMPORTANT: PACK_SESIUNE.SETAN și SETLUNA trebuie executate în ACEEAȘI
        tranzacție cu SELECT-ul din vbancasa* views!

        Folosim un bloc PL/SQL anonim care:
        1. Obține anul și luna curentă din calendar
        2. Apelează PACK_SESIUNE.SETAN și SETLUNA
        3. Execută SELECT-ul din vbancasa*
        Toate în aceeași tranzacție!
        """
        company_id = int(filter_params.company)
        schema = await TreasuryService._get_schema(company_id)

        async with oracle_pool.get_connection() as connection:
            with connection.cursor() as cursor:

                # Construiește query-ul pentru tipul de registru selectat
                base_select = TreasuryService._get_view_query(schema, filter_params.register_type)

                # Construiește WHERE conditions
                where_conditions = []

                # Date filter preserves NULL-date rows (opening balance)
                # for correct cumulative sum calculation
                if filter_params.date_from and filter_params.date_to:
                    where_conditions.append(f"(dataact IS NULL OR (dataact >= TO_DATE('{filter_params.date_from.strftime('%Y-%m-%d')}', 'YYYY-MM-DD') AND dataact <= TO_DATE('{filter_params.date_to.strftime('%Y-%m-%d')}', 'YYYY-MM-DD')))")
                elif filter_params.date_from:
                    where_conditions.append(f"(dataact IS NULL OR dataact >= TO_DATE('{filter_params.date_from.strftime('%Y-%m-%d')}', 'YYYY-MM-DD'))")
                elif filter_params.date_to:
                    where_conditions.append(f"(dataact IS NULL OR dataact <= TO_DATE('{filter_params.date_to.strftime('%Y-%m-%d')}', 'YYYY-MM-DD'))")

                if filter_params.partner_name:
                    # Escape single quotes pentru SQL
                    partner_escaped = filter_params.partner_name.replace("'", "''")
                    where_conditions.append(f"UPPER(nume) LIKE UPPER('%{partner_escaped}%')")

                if filter_params.bank_account:
                    # Escape single quotes pentru SQL
                    bank_escaped = filter_params.bank_account.replace("'", "''")
                    where_conditions.append(f"bancasa = '{bank_escaped}'")

                where_clause = ""
                if where_conditions:
                    where_clause = " WHERE " + " AND ".join(where_conditions)

                # Paginare Oracle
                offset = (filter_params.page - 1) * filter_params.page_size
                limit_val = filter_params.page_size

                # Determine period to use: from params or MAX from calendar
                if filter_params.luna and filter_params.an:
                    use_param_period = True
                    period_select = f"""
                        v_an := :param_an;
                        v_luna := :param_luna;
                    """
                else:
                    use_param_period = False
                    period_select = f"""
                        SELECT anul, luna INTO v_an, v_luna
                        FROM {schema}.calendar
                        WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar);
                    """

                # Bloc PL/SQL anonim care face totul într-o singură tranzacție:
                # 1. Obține anul și luna din params sau calendar
                # 2. Setează PACK_SESIUNE.SETAN și SETLUNA
                # 3. Returnează datele prin REF CURSOR
                # IMPORTANT: Folosim ROW_NUMBER() pentru paginare corectă cu ORDER BY NULLS FIRST
                plsql_block = f"""
                DECLARE
                    v_an NUMBER;
                    v_luna NUMBER;
                    v_cursor SYS_REFCURSOR;
                BEGIN
                    -- Obține anul și luna din parametri sau calendar
                    {period_select}

                    -- Setează contextul de sesiune (OBLIGATORIU înainte de SELECT din vbancasa*)
                    {schema}.PACK_SESIUNE.SETAN(v_an);
                    {schema}.PACK_SESIUNE.SETLUNA(v_luna);

                    -- Return accounting period
                    :out_an := v_an;
                    :out_luna := v_luna;

                    -- Returnează datele prin cursor cu ROW_NUMBER pentru paginare corectă
                    -- Pentru rânduri cu dataact=NULL (solduri precedente), sortare după bancasa
                    -- Pentru rânduri cu date, sortare după data, număr, bancasa
                    OPEN :result_cursor FOR
                        SELECT * FROM (
                            SELECT t.*, ROW_NUMBER() OVER (
                                ORDER BY dataact ASC NULLS FIRST,
                                         CASE WHEN dataact IS NULL THEN bancasa END ASC,
                                         nract ASC NULLS FIRST,
                                         bancasa ASC
                            ) as rn
                            FROM ({base_select}) t{where_clause}
                        ) WHERE rn > {offset} AND rn <= {offset + limit_val};
                END;
                """

                # Creează cursor pentru rezultate (oracledb.CURSOR pentru REF CURSOR)
                result_cursor = cursor.var(oracledb.CURSOR)
                out_an = cursor.var(int)
                out_luna = cursor.var(int)

                # Build params dict
                exec_params = {'result_cursor': result_cursor, 'out_an': out_an, 'out_luna': out_luna}
                if use_param_period:
                    exec_params['param_an'] = filter_params.an
                    exec_params['param_luna'] = filter_params.luna

                # Execută blocul PL/SQL cu REF CURSOR
                cursor.execute(plsql_block, exec_params)

                # Get accounting period values
                accounting_year = out_an.getvalue()
                accounting_month = out_luna.getvalue()

                # Obține rezultatele din cursor
                ref_cursor = result_cursor.getvalue()
                rows = ref_cursor.fetchall()
                ref_cursor.close()

                # Pentru count total, executăm alt bloc PL/SQL
                count_plsql = f"""
                DECLARE
                    v_an NUMBER;
                    v_luna NUMBER;
                BEGIN
                    -- Obține anul și luna din parametri sau calendar
                    {period_select}

                    {schema}.PACK_SESIUNE.SETAN(v_an);
                    {schema}.PACK_SESIUNE.SETLUNA(v_luna);

                    SELECT COUNT(*) INTO :total_count FROM ({base_select}) sub{where_clause};
                END;
                """

                total_count_var = cursor.var(int)
                count_params = {'total_count': total_count_var}
                if use_param_period:
                    count_params['param_an'] = filter_params.an
                    count_params['param_luna'] = filter_params.luna
                cursor.execute(count_plsql, count_params)
                total_count = total_count_var.getvalue()

                # Query pentru TOTALURI din TOATE înregistrările filtrate (nu doar pagina curentă)
                # sold_precedent = suma sold pentru rânduri cu dataact IS NULL
                # total_incasari = suma incasari pentru rânduri cu dataact IS NOT NULL
                # total_plati = suma plati pentru rânduri cu dataact IS NOT NULL
                # Notă: where_clause poate fi gol sau poate conține "WHERE ..."
                # Dacă e gol, adăugăm WHERE; dacă nu, adăugăm AND
                dataact_null_cond = " AND dataact IS NULL" if where_clause else " WHERE dataact IS NULL"
                dataact_not_null_cond = " AND dataact IS NOT NULL" if where_clause else " WHERE dataact IS NOT NULL"

                totals_plsql = f"""
                DECLARE
                    v_an NUMBER;
                    v_luna NUMBER;
                BEGIN
                    -- Obține anul și luna din parametri sau calendar
                    {period_select}

                    {schema}.PACK_SESIUNE.SETAN(v_an);
                    {schema}.PACK_SESIUNE.SETLUNA(v_luna);

                    -- Sold precedent: suma sold pentru rânduri fără dată (opening balance)
                    SELECT NVL(SUM(sold), 0) INTO :sold_precedent_all
                    FROM ({base_select}) sub{where_clause}{dataact_null_cond};

                    -- Total încasări: suma incasari pentru rânduri cu dată (transactions)
                    SELECT NVL(SUM(incasari), 0) INTO :total_incasari_all
                    FROM ({base_select}) sub{where_clause}{dataact_not_null_cond};

                    -- Total plăți: suma plati pentru rânduri cu dată (transactions)
                    SELECT NVL(SUM(plati), 0) INTO :total_plati_all
                    FROM ({base_select}) sub{where_clause}{dataact_not_null_cond};
                END;
                """

                sold_precedent_all_var = cursor.var(oracledb.NUMBER)
                total_incasari_all_var = cursor.var(oracledb.NUMBER)
                total_plati_all_var = cursor.var(oracledb.NUMBER)

                totals_params = {
                    'sold_precedent_all': sold_precedent_all_var,
                    'total_incasari_all': total_incasari_all_var,
                    'total_plati_all': total_plati_all_var
                }
                if use_param_period:
                    totals_params['param_an'] = filter_params.an
                    totals_params['param_luna'] = filter_params.luna

                cursor.execute(totals_plsql, totals_params)

                sold_precedent_all = Decimal(str(sold_precedent_all_var.getvalue() or 0))
                total_incasari_all = Decimal(str(total_incasari_all_var.getvalue() or 0))
                total_plati_all = Decimal(str(total_plati_all_var.getvalue() or 0))
                sold_final_all = sold_precedent_all + total_incasari_all - total_plati_all

                # Procesare rezultate
                registers = []
                total_incasari = Decimal('0.00')
                total_plati = Decimal('0.00')

                for row in rows:
                    # Coloane: nume, nract, dataact, bancasa, incasari, plati, sold, valuta, tip_registru, explicatia, rn
                    # row[0-9] = date, row[10] = rn (ROW_NUMBER la final)
                    register_data = BankCashRegister(
                        nume=row[0] or '',
                        nract=row[1],
                        dataact=row[2],
                        nume_cont_bancar=row[3] or '',
                        incasari=Decimal(str(row[4] or 0)),
                        plati=Decimal(str(row[5] or 0)),
                        sold=Decimal(str(row[6] or 0)),
                        valuta=row[7],
                        tip_registru=row[8],
                        explicatia=row[9] or ''
                    )
                    registers.append(register_data)
                    total_incasari += register_data.incasari
                    total_plati += register_data.plati

                logger.info(f"Treasury query for company {company_id}, type={filter_params.register_type}: {len(registers)} records, total={total_count}")

                return RegisterListResponse(
                    registers=registers,
                    total_count=total_count,
                    filtered_count=len(registers),
                    total_incasari=total_incasari,
                    total_plati=total_plati,
                    page=filter_params.page,
                    page_size=filter_params.page_size,
                    has_more=len(registers) == filter_params.page_size,
                    accounting_period=AccountingPeriod(an=accounting_year, luna=accounting_month),
                    # Totaluri din TOATE înregistrările filtrate
                    sold_precedent_all=sold_precedent_all,
                    total_incasari_all=total_incasari_all,
                    total_plati_all=total_plati_all,
                    sold_final_all=sold_final_all
                )

    @staticmethod
    @cached(cache_type='treasury', key_params=['company_id', 'register_type'])
    async def get_bank_cash_accounts(company_id: int, register_type: str) -> List[str]:
        """
        Obține lista distinctă de conturi bancă/casă (bancasa) pentru dropdown.
        Cached pentru performanță.
        IMPORTANT: Trebuie să setăm contextul PACK_SESIUNE înainte de a accesa vbancasa views!
        """
        schema = await TreasuryService._get_schema(company_id)

        # Map register_type to view
        view_map = {
            'BANCA_LEI': f'{schema}.vbancasa_5121_cum',
            'BANCA_VALUTA': f'{schema}.vbancasa_5124_cum',
            'CASA_LEI': f'{schema}.vbancasa_5311_cum',
            'CASA_VALUTA': f'{schema}.vbancasa_5314_cum'
        }

        if register_type not in view_map:
            return []

        view_name = view_map[register_type]

        async with oracle_pool.get_connection() as connection:
            with connection.cursor() as cursor:
                # PL/SQL block to set session context and get accounts
                plsql_block = f"""
                DECLARE
                    v_an NUMBER;
                    v_luna NUMBER;
                BEGIN
                    -- Get current year and month from calendar
                    SELECT anul, luna INTO v_an, v_luna
                    FROM {schema}.calendar
                    WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar);

                    -- Set session context (REQUIRED before accessing vbancasa* views)
                    {schema}.PACK_SESIUNE.SETAN(v_an);
                    {schema}.PACK_SESIUNE.SETLUNA(v_luna);

                    -- Return accounts via cursor
                    OPEN :result_cursor FOR
                        SELECT DISTINCT bancasa
                        FROM {view_name}
                        WHERE bancasa IS NOT NULL
                        ORDER BY bancasa;
                END;
                """

                result_cursor = cursor.var(oracledb.CURSOR)
                cursor.execute(plsql_block, {'result_cursor': result_cursor})

                ref_cursor = result_cursor.getvalue()
                rows = ref_cursor.fetchall()
                ref_cursor.close()

                accounts = [row[0] for row in rows if row[0]]
                logger.info(f"Found {len(accounts)} bank/cash accounts for company {company_id}, type={register_type}")
                return accounts
