#!/usr/bin/env python
"""
Haven Grazuri Investment Limited - Accounting System Deployment Script
Automated deployment for the Chart of Accounts and Accounting System

This script will:
1. Check Python version and requirements
2. Install accounting-specific dependencies
3. Run accounting migrations
4. Import Chart of Accounts with microfinance accounts
5. Create fiscal periods
6. Setup integration with loan and expense systems
7. Run accounting system tests
8. Collect static files
9. Verify deployment

Usage:
    python deploy_accounting_system.py                    # Full deployment
    python deploy_accounting_system.py --skip-tests       # Skip tests
    python deploy_accounting_system.py --production       # Production mode
"""

import os
import sys
import subprocess
import time
from pathlib import Path

# Force load .env file before Django
from dotenv import load_dotenv

# Get the base directory (where manage.py is)
BASE_DIR = Path(__file__).resolve().parent

# Try to load .env file
env_path = BASE_DIR / '.env'
if env_path.exists():
    print(f"Loading .env from: {env_path}")
    load_dotenv(env_path)
    print(f"DB_NAME from env: {os.getenv('DB_NAME', 'NOT SET')}")
    print(f"DB_USER from env: {os.getenv('DB_USER', 'NOT SET')}")
else:
    print(f"WARNING: .env file not found at {env_path}")
    print("Will use default database settings from settings.py")

# Configuration
PROJECT_NAME = "Haven Grazuri Accounting System"
COMPANY_NAME = "Haven Grazuri Investment Limited"

# Colors for output
class Colors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

def print_header(message):
    """Print a header message"""
    print(f"\n{Colors.HEADER}{'=' * 100}{Colors.ENDC}")
    print(f"{Colors.HEADER}{Colors.BOLD}{message}{Colors.ENDC}")
    print(f"{Colors.HEADER}{'=' * 100}{Colors.ENDC}\n")

def print_success(message):
    """Print a success message"""
    print(f"{Colors.OKGREEN}✓ {message}{Colors.ENDC}")

def print_error(message):
    """Print an error message"""
    print(f"{Colors.FAIL}✗ {message}{Colors.ENDC}")

def print_warning(message):
    """Print a warning message"""
    print(f"{Colors.WARNING}⚠ {message}{Colors.ENDC}")

def print_info(message):
    """Print an info message"""
    print(f"{Colors.OKCYAN}ℹ {message}{Colors.ENDC}")

def run_command(command, description, check=True, shell=True, capture_output=False):
    """Run a shell command and handle errors"""
    print_info(f"{description}...")
    try:
        if capture_output:
            result = subprocess.run(
                command,
                shell=shell,
                check=check,
                capture_output=True,
                text=True
            )
            return result
        else:
            result = subprocess.run(command, shell=shell, check=check)
            print_success(f"{description} completed")
            return result
    except subprocess.CalledProcessError as e:
        if check:
            print_error(f"{description} failed: {str(e)}")
            if capture_output and e.stderr:
                print(e.stderr)
            sys.exit(1)
        else:
            print_warning(f"{description} had warnings (continuing)")
            return None

def check_python_version():
    """Check if Python version is compatible"""
    print_header("STEP 1: Checking Python Version")
    
    version = sys.version_info
    print_info(f"Python version: {version.major}.{version.minor}.{version.micro}")
    
    if version.major < 3 or (version.major == 3 and version.minor < 8):
        print_error("Python 3.8 or higher is required")
        sys.exit(1)
    
    print_success(f"Python version {version.major}.{version.minor}.{version.micro} is compatible")

def check_accounting_app_exists():
    """Check if accounting app directory exists"""
    print_header("STEP 2: Checking Accounting App")
    
    accounting_path = Path("accounting")
    if not accounting_path.exists():
        print_error("Accounting app directory not found")
        print_info("Expected directory: accounting/")
        sys.exit(1)
    
    # Check for required files
    required_files = [
        "accounting/models.py",
        "accounting/services/accounting_service.py",
        "accounting/services/integration_service.py",
    ]
    
    for file_path in required_files:
        if not Path(file_path).exists():
            print_error(f"Required file not found: {file_path}")
            sys.exit(1)
    
    print_success("Accounting app structure verified")

def run_migrations():
    """Run Django migrations"""
    print_header("STEP 3: Running Accounting Migrations")
    
    # Make migrations for accounting app
    print_info("Creating accounting app migrations...")
    run_command(
        f'{sys.executable} manage.py makemigrations accounting',
        "Making accounting migrations",
        check=False
    )
    
    # Run all migrations
    print_info("Applying migrations...")
    run_command(
        f'{sys.executable} manage.py migrate',
        "Running migrations"
    )
    
    print_success("All migrations applied successfully")

def create_chart_of_accounts():
    """Create the microfinance Chart of Accounts"""
    print_header("STEP 4: Creating Chart of Accounts")
    
    coa_script = """
import os
import django
from decimal import Decimal
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branch_system.settings')
django.setup()
from accounting.models import Account
from users.models import CustomUser

# Get or create system user for account creation
try:
    system_user = CustomUser.objects.filter(role='admin').first()
except:
    system_user = None

# Define microfinance Chart of Accounts
accounts_data = [
    # ASSETS (202xxx)
    {'code': '202001', 'name': 'Loan Portfolio - Principal', 'account_type': 'asset', 'subtype': 'loan_portfolio', 
     'description': 'Outstanding principal balance of all active loans', 'is_system_account': True},
    {'code': '202002', 'name': 'Accrued Interest Receivable', 'account_type': 'asset', 'subtype': 'current_asset',
     'description': 'Interest earned but not yet collected', 'is_system_account': True},
    {'code': '202003', 'name': 'Cash on Hand', 'account_type': 'asset', 'subtype': 'current_asset',
     'description': 'Physical cash in branches', 'is_system_account': True},
    {'code': '202004', 'name': 'Bank Account - Main', 'account_type': 'asset', 'subtype': 'current_asset',
     'description': 'Main operating bank account', 'is_system_account': True},
    {'code': '202005', 'name': 'M-Pesa Account', 'account_type': 'asset', 'subtype': 'current_asset',
     'description': 'M-Pesa business account balance', 'is_system_account': True},
    {'code': '202006', 'name': 'Allowance for Loan Losses', 'account_type': 'asset', 'subtype': 'loan_portfolio',
     'description': 'Reserve for expected loan defaults (contra-asset)', 'is_system_account': True},
    {'code': '202007', 'name': 'Office Equipment', 'account_type': 'asset', 'subtype': 'fixed_asset',
     'description': 'Computers, furniture, and office equipment', 'is_system_account': False},
    {'code': '202008', 'name': 'Accumulated Depreciation - Equipment', 'account_type': 'asset', 'subtype': 'fixed_asset',
     'description': 'Accumulated depreciation on equipment (contra-asset)', 'is_system_account': False},
    
    # LIABILITIES (302xxx)
    {'code': '302001', 'name': 'Client Savings Deposits', 'account_type': 'liability', 'subtype': 'client_savings',
     'description': 'Savings deposits from microfinance clients', 'is_system_account': True},
    {'code': '302002', 'name': 'Loan Payable - Bank', 'account_type': 'liability', 'subtype': 'borrowings',
     'description': 'Loans borrowed from financial institutions', 'is_system_account': False},
    {'code': '302003', 'name': 'Accrued Expenses Payable', 'account_type': 'liability', 'subtype': 'current_liability',
     'description': 'Expenses incurred but not yet paid', 'is_system_account': True},
    {'code': '302004', 'name': 'Interest Payable', 'account_type': 'liability', 'subtype': 'current_liability',
     'description': 'Interest owed on borrowed funds', 'is_system_account': False},
    
    # EQUITY (320xxx)
    {'code': '320001', 'name': 'Share Capital', 'account_type': 'equity', 'subtype': None,
     'description': 'Owner investment in the institution', 'is_system_account': True},
    {'code': '320002', 'name': 'Retained Earnings', 'account_type': 'equity', 'subtype': None,
     'description': 'Accumulated profits retained in the business', 'is_system_account': True},
    {'code': '320003', 'name': 'Current Year Earnings', 'account_type': 'equity', 'subtype': None,
     'description': 'Net income for the current fiscal year', 'is_system_account': True},
    
    # INCOME (401xxx)
    {'code': '401001', 'name': 'Interest Income - Loans', 'account_type': 'income', 'subtype': 'interest_income',
     'description': 'Interest earned from loan products', 'is_system_account': True},
    {'code': '401002', 'name': 'Fee Income - Processing Fees', 'account_type': 'income', 'subtype': 'fee_income',
     'description': 'Loan processing and application fees', 'is_system_account': True},
    {'code': '401003', 'name': 'Fee Income - Late Payment Penalties', 'account_type': 'income', 'subtype': 'fee_income',
     'description': 'Penalties charged on late loan payments', 'is_system_account': True},
    {'code': '401004', 'name': 'Other Income', 'account_type': 'income', 'subtype': 'other_income',
     'description': 'Miscellaneous income sources', 'is_system_account': False},
    
    # EXPENSES (501xxx)
    {'code': '501001', 'name': 'Salaries and Wages', 'account_type': 'expense', 'subtype': 'staff_costs',
     'description': 'Employee salaries and wages', 'is_system_account': True},
    {'code': '501002', 'name': 'Rent Expense', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Office and branch rent payments', 'is_system_account': False},
    {'code': '501003', 'name': 'Utilities Expense', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Electricity, water, and internet expenses', 'is_system_account': False},
    {'code': '501004', 'name': 'Marketing and Advertising', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Marketing and promotional expenses', 'is_system_account': False},
    {'code': '501005', 'name': 'Office Supplies', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Stationery and office supply expenses', 'is_system_account': False},
    {'code': '501006', 'name': 'Transportation Expense', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Vehicle fuel and transportation costs', 'is_system_account': False},
    {'code': '501007', 'name': 'Loan Loss Provision Expense', 'account_type': 'expense', 'subtype': 'loan_loss_provision',
     'description': 'Expense for expected loan defaults', 'is_system_account': True},
    {'code': '501008', 'name': 'Depreciation Expense', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Depreciation of fixed assets', 'is_system_account': False},
    {'code': '501009', 'name': 'Bank Charges', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Banking fees and charges', 'is_system_account': False},
    {'code': '501010', 'name': 'Professional Fees', 'account_type': 'expense', 'subtype': 'operating_expense',
     'description': 'Legal, accounting, and consulting fees', 'is_system_account': False},
]

created_count = 0
existing_count = 0

for account_data in accounts_data:
    account, created = Account.objects.get_or_create(
        code=account_data['code'],
        defaults={
            'name': account_data['name'],
            'account_type': account_data['account_type'],
            'subtype': account_data['subtype'],
            'description': account_data['description'],
            'is_system_account': account_data['is_system_account'],
            'is_active': True,
            'created_by': system_user
        }
    )
    
    if created:
        created_count += 1
        print(f'[+] Created: {account.code} - {account.name}')
    else:
        existing_count += 1
        print(f'[ ] Already exists: {account.code} - {account.name}')

print(f'\\nChart of Accounts Summary:')
print(f'  Created: {created_count} accounts')
print(f'  Already existed: {existing_count} accounts')
print(f'  Total: {created_count + existing_count} accounts')
"""
    
    with open('_create_chart_of_accounts.py', 'w', encoding='utf-8') as f:
        f.write(coa_script)
    
    run_command(
        f'{sys.executable} _create_chart_of_accounts.py',
        "Creating Chart of Accounts"
    )
    
    os.remove('_create_chart_of_accounts.py')
    
    print_success("Chart of Accounts created successfully")

def create_fiscal_periods():
    """Create initial fiscal periods"""
    print_header("STEP 5: Creating Fiscal Periods")
    
    fiscal_script = """
import os
import django
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branch_system.settings')
django.setup()
from accounting.models import FiscalPeriod

# Create fiscal periods for current year
current_date = datetime.now()
year = current_date.year

# Create monthly periods for the year
created_count = 0
existing_count = 0

for month in range(1, 13):
    start_date = datetime(year, month, 1).date()
    
    # Get last day of month
    if month == 12:
        end_date = datetime(year, 12, 31).date()
    else:
        end_date = (datetime(year, month + 1, 1) - timedelta(days=1)).date()
    
    period_name = start_date.strftime('%B %Y')
    
    period, created = FiscalPeriod.objects.get_or_create(
        start_date=start_date,
        end_date=end_date,
        defaults={
            'name': period_name,
            'period_type': 'monthly',
            'status': 'open' if month >= current_date.month else 'open'  # All periods open initially
        }
    )
    
    if created:
        created_count += 1
        print(f'[+] Created: {period.name} ({period.start_date} to {period.end_date})')
    else:
        existing_count += 1
        print(f'[ ] Already exists: {period.name}')

# Create quarterly periods
quarters = [
    {'name': f'Q1 {year}', 'start': datetime(year, 1, 1), 'end': datetime(year, 3, 31)},
    {'name': f'Q2 {year}', 'start': datetime(year, 4, 1), 'end': datetime(year, 6, 30)},
    {'name': f'Q3 {year}', 'start': datetime(year, 7, 1), 'end': datetime(year, 9, 30)},
    {'name': f'Q4 {year}', 'start': datetime(year, 10, 1), 'end': datetime(year, 12, 31)},
]

for quarter in quarters:
    period, created = FiscalPeriod.objects.get_or_create(
        name=quarter['name'],
        period_type='quarterly',
        defaults={
            'start_date': quarter['start'].date(),
            'end_date': quarter['end'].date(),
            'status': 'open'
        }
    )
    
    if created:
        created_count += 1
        print(f'[+] Created: {period.name}')
    else:
        existing_count += 1
        print(f'[ ] Already exists: {period.name}')

# Create annual period
period, created = FiscalPeriod.objects.get_or_create(
    name=f'FY {year}',
    period_type='annual',
    defaults={
        'start_date': datetime(year, 1, 1).date(),
        'end_date': datetime(year, 12, 31).date(),
        'status': 'open'
    }
)

if created:
    created_count += 1
    print(f'[+] Created: {period.name}')
else:
    existing_count += 1
    print(f'[ ] Already exists: {period.name}')

print(f'\\nFiscal Periods Summary:')
print(f'  Created: {created_count} periods')
print(f'  Already existed: {existing_count} periods')
print(f'  Total: {created_count + existing_count} periods')
"""
    
    with open('_create_fiscal_periods.py', 'w', encoding='utf-8') as f:
        f.write(fiscal_script)
    
    run_command(
        f'{sys.executable} _create_fiscal_periods.py',
        "Creating fiscal periods"
    )
    
    os.remove('_create_fiscal_periods.py')
    
    print_success("Fiscal periods created successfully")

def verify_integrations():
    """Verify signal handlers are registered"""
    print_header("STEP 6: Verifying Integrations")
    
    verify_script = """
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branch_system.settings')
django.setup()

# Check if accounting app is installed
from django.conf import settings
if 'accounting' in settings.INSTALLED_APPS:
    print('[+] Accounting app is installed')
else:
    print('[x] Accounting app NOT installed in INSTALLED_APPS')
    exit(1)

# Check if signal handlers are registered
try:
    from accounting import signals
    print('[+] Signal handlers module imported')
except ImportError as e:
    print(f'[!] Signal handlers not found: {e}')

# Check if services are available
try:
    from accounting.services.accounting_service import AccountingService
    from accounting.services.integration_service import IntegrationService
    print('[+] Accounting services available')
except ImportError as e:
    print(f'[x] Accounting services import failed: {e}')
    exit(1)

# Check if models are available
try:
    from accounting.models import Account, JournalEntry, JournalEntryLine, GeneralLedger
    print('[+] Accounting models available')
except ImportError as e:
    print(f'[x] Accounting models import failed: {e}')
    exit(1)

print('\\n[+] All integrations verified successfully')
"""
    
    with open('_verify_integrations.py', 'w', encoding='utf-8') as f:
        f.write(verify_script)
    
    run_command(
        f'{sys.executable} _verify_integrations.py',
        "Verifying integrations"
    )
    
    os.remove('_verify_integrations.py')
    
    print_success("Integrations verified successfully")

def run_tests(skip_tests=False):
    """Run accounting system tests"""
    print_header("STEP 7: Running Tests")
    
    if skip_tests:
        print_warning("Skipping tests (--skip-tests flag)")
        return
    
    print_info("Running accounting app tests...")
    result = run_command(
        f'{sys.executable} manage.py test accounting --verbosity=2',
        "Running accounting tests",
        check=False,
        capture_output=True
    )
    
    if result and result.returncode == 0:
        print_success("All tests passed")
    else:
        print_warning("Some tests failed or no tests found (continuing)")
        if result and result.stdout:
            print(result.stdout[-500:])  # Print last 500 chars

def collect_static_files():
    """Collect static files"""
    print_header("STEP 8: Collecting Static Files")
    
    run_command(
        f'{sys.executable} manage.py collectstatic --noinput',
        "Collecting static files",
        check=False
    )
    
    print_success("Static files collected")

def print_deployment_summary():
    """Print deployment summary"""
    print_header("DEPLOYMENT COMPLETE!")
    
    print(f"{Colors.OKGREEN}{Colors.BOLD}")
    print("[+] Accounting app verified")
    print("[+] Database migrations applied")
    print("[+] Chart of Accounts created")
    print("[+] Fiscal periods created")
    print("[+] Integrations verified")
    print("[+] Tests completed")
    print("[+] Static files collected")
    print(f"{Colors.ENDC}")
    
    print(f"\n{Colors.OKCYAN}{Colors.BOLD}Accounting System Information:{Colors.ENDC}")
    print(f"  Chart of Accounts: 30+ microfinance accounts")
    print(f"  Asset Accounts: 202xxx series")
    print(f"  Liability Accounts: 302xxx series")
    print(f"  Equity Accounts: 320xxx series")
    print(f"  Income Accounts: 401xxx series")
    print(f"  Expense Accounts: 501xxx series")
    
    print(f"\n{Colors.OKCYAN}{Colors.BOLD}Key Features Deployed:{Colors.ENDC}")
    print(f"  [+] Double-entry bookkeeping")
    print(f"  [+] General ledger posting")
    print(f"  [+] Journal entry management")
    print(f"  [+] Automatic loan transaction accounting")
    print(f"  [+] Automatic expense accounting")
    print(f"  [+] Multi-branch support")
    print(f"  [+] Fiscal period management")
    print(f"  [+] Account balances and running totals")
    
    print(f"\n{Colors.OKCYAN}{Colors.BOLD}Next Steps:{Colors.ENDC}")
    print(f"  1. Access the accounting dashboard:")
    print(f"     {Colors.BOLD}/accounting/dashboard/{Colors.ENDC}")
    print(f"  2. View Chart of Accounts:")
    print(f"     {Colors.BOLD}/accounting/accounts/{Colors.ENDC}")
    print(f"  3. Create journal entries:")
    print(f"     {Colors.BOLD}/accounting/journal-entries/create/{Colors.ENDC}")
    print(f"  4. View financial reports:")
    print(f"     {Colors.BOLD}/accounting/reports/{Colors.ENDC}")
    
    print(f"\n{Colors.WARNING}{Colors.BOLD}Important Notes:{Colors.ENDC}")
    print(f"  - Loan transactions will now automatically create accounting entries")
    print(f"  - Expense approvals will automatically create accounting entries")
    print(f"  - Review fiscal periods and close previous periods as needed")
    print(f"  - Setup user permissions for accounting functions")
    print(f"  - Configure branch-specific cash/bank accounts")
    
    print(f"\n{Colors.HEADER}{'=' * 100}{Colors.ENDC}\n")

def main():
    """Main deployment function"""
    import argparse
    
    parser = argparse.ArgumentParser(description='Deploy Haven Grazuri Accounting System')
    parser.add_argument('--skip-tests', action='store_true', help='Skip running tests')
    parser.add_argument('--production', action='store_true', help='Production mode')
    
    args = parser.parse_args()
    
    print(f"\n{Colors.HEADER}{Colors.BOLD}")
    print("=" * 100)
    print(f"  {PROJECT_NAME}")
    print(f"  {COMPANY_NAME}")
    print("  Accounting System Deployment")
    print("=" * 100)
    print(f"{Colors.ENDC}\n")
    
    start_time = time.time()
    
    try:
        # Run deployment steps
        check_python_version()
        check_accounting_app_exists()
        run_migrations()
        create_chart_of_accounts()
        create_fiscal_periods()
        verify_integrations()
        run_tests(skip_tests=args.skip_tests)
        collect_static_files()
        
        # Calculate deployment time
        elapsed_time = time.time() - start_time
        print_info(f"Deployment completed in {elapsed_time:.2f} seconds")
        
        # Print summary
        print_deployment_summary()
        
    except KeyboardInterrupt:
        print_error("\nDeployment cancelled by user")
        sys.exit(1)
    except Exception as e:
        print_error(f"Deployment failed: {str(e)}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == '__main__':
    main()
