import * as plugins from './plugins.js';
import { getDb, closeDb } from './skr.database.js';
import { Account } from './skr.classes.account.js';
import { Transaction } from './skr.classes.transaction.js';
import { JournalEntry } from './skr.classes.journalentry.js';
import { SKR03_ACCOUNTS, SKR03_ACCOUNT_CLASSES } from './skr03.data.js';
import { SKR04_ACCOUNTS, SKR04_ACCOUNT_CLASSES } from './skr04.data.js';
import type {
  IDatabaseConfig,
  TSKRType,
  IAccountData,
  IAccountFilter,
  ITransactionFilter,
  ITransactionData,
  IJournalEntry,
} from './skr.types.js';

export class ChartOfAccounts {
  private logger: plugins.smartlog.Smartlog;
  private initialized: boolean = false;
  private skrType: TSKRType | null = null;

  constructor(private config?: IDatabaseConfig) {
    this.logger = new plugins.smartlog.Smartlog({
      logContext: {
        company: 'fin.cx',
        companyunit: 'skr',
        containerName: 'ChartOfAccounts',
        environment: 'local',
        runtime: 'node',
        zone: 'local',
      },
    });
    this.logger.enableConsole();
  }

  /**
   * Initialize the database connection
   */
  public async init(): Promise<void> {
    if (this.initialized) {
      this.logger.log('info', 'ChartOfAccounts already initialized');
      return;
    }

    if (!this.config) {
      throw new Error('Database configuration required for initialization');
    }

    await getDb(this.config);
    this.initialized = true;
    this.logger.log('info', 'ChartOfAccounts initialized successfully');
  }

  /**
   * Initialize SKR03 chart of accounts
   */
  public async initializeSKR03(): Promise<void> {
    await this.init();

    this.logger.log('info', 'Initializing SKR03 chart of accounts');

    // Check if SKR03 accounts already exist
    const existingAccounts = await Account.getInstances({ skrType: 'SKR03' });
    if (existingAccounts.length > 0) {
      this.logger.log(
        'info',
        `SKR03 already initialized with ${existingAccounts.length} accounts`,
      );
      this.skrType = 'SKR03';
      return;
    }

    // Create all SKR03 accounts
    const accounts: Account[] = [];
    for (const accountData of SKR03_ACCOUNTS) {
      const account = await Account.createAccount(accountData);
      accounts.push(account);
    }

    this.skrType = 'SKR03';
    this.logger.log(
      'info',
      `Successfully initialized SKR03 with ${accounts.length} accounts`,
    );
  }

  /**
   * Initialize SKR04 chart of accounts
   */
  public async initializeSKR04(): Promise<void> {
    await this.init();

    this.logger.log('info', 'Initializing SKR04 chart of accounts');

    // Check if SKR04 accounts already exist
    const existingAccounts = await Account.getInstances({ skrType: 'SKR04' });
    if (existingAccounts.length > 0) {
      this.logger.log(
        'info',
        `SKR04 already initialized with ${existingAccounts.length} accounts`,
      );
      this.skrType = 'SKR04';
      return;
    }

    // Create all SKR04 accounts
    const accounts: Account[] = [];
    for (const accountData of SKR04_ACCOUNTS) {
      const account = await Account.createAccount(accountData);
      accounts.push(account);
    }

    this.skrType = 'SKR04';
    this.logger.log(
      'info',
      `Successfully initialized SKR04 with ${accounts.length} accounts`,
    );
  }

  /**
   * Get the current SKR type
   */
  public getSKRType(): TSKRType | null {
    return this.skrType;
  }

  /**
   * Set the active SKR type
   */
  public setSKRType(skrType: TSKRType): void {
    this.skrType = skrType;
  }

  /**
   * Get account by number
   */
  public async getAccountByNumber(
    accountNumber: string,
  ): Promise<Account | null> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    return await Account.getAccountByNumber(accountNumber, this.skrType);
  }

  /**
   * Get accounts by class
   */
  public async getAccountsByClass(accountClass: number): Promise<Account[]> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    return await Account.getAccountsByClass(accountClass, this.skrType);
  }

  /**
   * Get accounts by type
   */
  public async getAccountsByType(
    accountType: IAccountData['accountType'],
  ): Promise<Account[]> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    return await Account.getAccountsByType(accountType, this.skrType);
  }

  /**
   * Create a custom account
   */
  public async createCustomAccount(
    accountData: Partial<IAccountData>,
  ): Promise<Account> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    // Ensure the account uses the current SKR type
    const fullAccountData: IAccountData = {
      accountNumber: accountData.accountNumber || '',
      accountName: accountData.accountName || '',
      accountClass: accountData.accountClass || 0,
      accountType: accountData.accountType || 'asset',
      skrType: this.skrType,
      description: accountData.description,
      vatRate: accountData.vatRate,
      isActive:
        accountData.isActive !== undefined ? accountData.isActive : true,
    };

    // Validate account number doesn't already exist
    const existing = await this.getAccountByNumber(
      fullAccountData.accountNumber,
    );
    if (existing) {
      throw new Error(
        `Account ${fullAccountData.accountNumber} already exists`,
      );
    }

    return await Account.createAccount(fullAccountData);
  }

  /**
   * Update an existing account
   */
  public async updateAccount(
    accountNumber: string,
    updates: Partial<IAccountData>,
  ): Promise<Account> {
    const account = await this.getAccountByNumber(accountNumber);
    if (!account) {
      throw new Error(`Account ${accountNumber} not found`);
    }

    // Apply updates
    if (updates.accountName !== undefined)
      account.accountName = updates.accountName;
    if (updates.description !== undefined)
      account.description = updates.description;
    if (updates.vatRate !== undefined) account.vatRate = updates.vatRate;
    if (updates.isActive !== undefined) account.isActive = updates.isActive;

    account.updatedAt = new Date();
    await account.save();

    return account;
  }

  /**
   * Delete a custom account (only non-system accounts)
   */
  public async deleteAccount(accountNumber: string): Promise<void> {
    const account = await this.getAccountByNumber(accountNumber);
    if (!account) {
      throw new Error(`Account ${accountNumber} not found`);
    }

    if (account.isSystemAccount) {
      throw new Error(`Cannot delete system account ${accountNumber}`);
    }

    // Check if account has transactions
    const transactions = await Transaction.getTransactionsByAccount(
      accountNumber,
      account.skrType,
    );
    if (transactions.length > 0) {
      throw new Error(
        `Cannot delete account ${accountNumber} with existing transactions`,
      );
    }

    await account.delete();
  }

  /**
   * Search accounts
   */
  public async searchAccounts(searchTerm: string): Promise<Account[]> {
    return await Account.searchAccounts(searchTerm, this.skrType);
  }

  /**
   * Get all accounts
   */
  public async getAllAccounts(filter?: IAccountFilter): Promise<Account[]> {
    const query: any = {};

    if (this.skrType) {
      query.skrType = this.skrType;
    }

    if (filter) {
      if (filter.accountClass !== undefined)
        query.accountClass = filter.accountClass;
      if (filter.accountType !== undefined)
        query.accountType = filter.accountType;
      if (filter.isActive !== undefined) query.isActive = filter.isActive;
    }

    const accounts = await Account.getInstances(query);

    // Apply text search if provided
    if (filter?.searchTerm) {
      const lowerSearchTerm = filter.searchTerm.toLowerCase();
      return accounts.filter(
        (account) =>
          account.accountNumber.includes(filter.searchTerm) ||
          account.accountName.toLowerCase().includes(lowerSearchTerm) ||
          account.description.toLowerCase().includes(lowerSearchTerm),
      );
    }

    return accounts;
  }

  /**
   * Post a simple transaction
   */
  public async postTransaction(
    transactionData: ITransactionData,
  ): Promise<Transaction> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    // Ensure the transaction uses the current SKR type
    const fullTransactionData: ITransactionData = {
      ...transactionData,
      skrType: this.skrType,
    };

    return await Transaction.createTransaction(fullTransactionData);
  }

  /**
   * Post a journal entry
   */
  public async postJournalEntry(
    journalData: IJournalEntry,
  ): Promise<JournalEntry> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    // Ensure the journal entry uses the current SKR type
    const fullJournalData: IJournalEntry = {
      ...journalData,
      skrType: this.skrType,
    };

    const journalEntry = await JournalEntry.createJournalEntry(fullJournalData);
    await journalEntry.post();

    return journalEntry;
  }

  /**
   * Get transactions for an account
   */
  public async getAccountTransactions(
    accountNumber: string,
  ): Promise<Transaction[]> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    return await Transaction.getTransactionsByAccount(
      accountNumber,
      this.skrType,
    );
  }

  /**
   * Get transactions by filter
   */
  public async getTransactions(
    filter?: ITransactionFilter,
  ): Promise<Transaction[]> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    const query: any = {
      skrType: this.skrType,
      status: 'posted',
    };

    if (filter) {
      if (filter.dateFrom || filter.dateTo) {
        query.date = {};
        if (filter.dateFrom) query.date.$gte = filter.dateFrom;
        if (filter.dateTo) query.date.$lte = filter.dateTo;
      }

      if (filter.accountNumber) {
        query.$or = [
          { debitAccount: filter.accountNumber },
          { creditAccount: filter.accountNumber },
        ];
      }

      if (filter.minAmount || filter.maxAmount) {
        query.amount = {};
        if (filter.minAmount) query.amount.$gte = filter.minAmount;
        if (filter.maxAmount) query.amount.$lte = filter.maxAmount;
      }
    }

    const transactions = await Transaction.getInstances(query);

    // Apply text search if provided
    if (filter?.searchTerm) {
      const lowerSearchTerm = filter.searchTerm.toLowerCase();
      return transactions.filter(
        (transaction) =>
          transaction.description.toLowerCase().includes(lowerSearchTerm) ||
          transaction.reference.toLowerCase().includes(lowerSearchTerm),
      );
    }

    return transactions;
  }

  /**
   * Reverse a transaction
   */
  public async reverseTransaction(transactionId: string): Promise<Transaction> {
    const transaction = await Transaction.getTransactionById(transactionId);
    if (!transaction) {
      throw new Error(`Transaction ${transactionId} not found`);
    }

    return await transaction.reverseTransaction();
  }

  /**
   * Get account class description
   */
  public getAccountClassDescription(accountClass: number): string {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    const classes =
      this.skrType === 'SKR03' ? SKR03_ACCOUNT_CLASSES : SKR04_ACCOUNT_CLASSES;
    return (
      classes[accountClass as keyof typeof classes] || `Class ${accountClass}`
    );
  }

  /**
   * Import accounts from CSV
   */
  public async importAccountsFromCSV(csvContent: string): Promise<number> {
    if (!this.skrType) {
      throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
    }

    const lines = csvContent.split('\n').filter((line) => line.trim());
    let importedCount = 0;

    for (const line of lines) {
      // Parse CSV line (expecting format: "account";"name";"description";"type";"active")
      const parts = line
        .split(';')
        .map((part) => part.replace(/"/g, '').trim());

      if (parts.length >= 5) {
        const accountData: IAccountData = {
          accountNumber: parts[0],
          accountName: parts[1],
          accountClass: parseInt(parts[0][0]),
          accountType: parts[3] as IAccountData['accountType'],
          skrType: this.skrType,
          description: parts[2],
          isActive:
            parts[4].toLowerCase() === 'standard' ||
            parts[4].toLowerCase() === 'active',
        };

        try {
          await this.createCustomAccount(accountData);
          importedCount++;
        } catch (error) {
          this.logger.log(
            'warn',
            `Failed to import account ${parts[0]}: ${error.message}`,
          );
        }
      }
    }

    return importedCount;
  }

  /**
   * Export accounts to CSV
   */
  public async exportAccountsToCSV(): Promise<string> {
    const accounts = await this.getAllAccounts();

    const csvLines: string[] = [];
    csvLines.push('"Account";"Name";"Description";"Type";"Active"');

    for (const account of accounts) {
      csvLines.push(
        `"${account.accountNumber}";"${account.accountName}";"${account.description}";"${account.accountType}";"${account.isActive ? 'Active' : 'Inactive'}"`,
      );
    }

    return csvLines.join('\n');
  }

  /**
   * Close the database connection
   */
  public async close(): Promise<void> {
    await closeDb();
    this.initialized = false;
    this.logger.log('info', 'ChartOfAccounts closed');
  }
}
