package processor

import (
	"context"
	"fmt"

	"apskel-pos-be/internal/appcontext"
	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/mappers"
	"apskel-pos-be/internal/models"

	"github.com/google/uuid"
)


type AccountProcessor interface {
	CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error)
	GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error)
	UpdateAccount(ctx context.Context, id uuid.UUID, req *models.UpdateAccountRequest) (*models.AccountResponse, error)
	DeleteAccount(ctx context.Context, id uuid.UUID) error
	ListAccounts(ctx context.Context, req *models.ListAccountsRequest) ([]models.AccountResponse, int, error)
	GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.AccountResponse, error)
	GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]models.AccountResponse, error)
	UpdateAccountBalance(ctx context.Context, id uuid.UUID, amount float64) error
	GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error)
}

type AccountProcessorImpl struct {
	accountRepo        AccountRepository
	chartOfAccountRepo ChartOfAccountRepository
}

func NewAccountProcessorImpl(accountRepo AccountRepository, chartOfAccountRepo ChartOfAccountRepository) *AccountProcessorImpl {
	return &AccountProcessorImpl{
		accountRepo:        accountRepo,
		chartOfAccountRepo: chartOfAccountRepo,
	}
}

func (p *AccountProcessorImpl) CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error) {
	// Get organization and outlet from context
	appCtx := appcontext.FromGinContext(ctx)
	organizationID := appCtx.OrganizationID
	var outletID *uuid.UUID
	if appCtx.OutletID != uuid.Nil {
		outletID = &appCtx.OutletID
	}

	// Check if account number already exists for this organization/outlet
	existing, err := p.accountRepo.GetByNumber(ctx, organizationID, req.Number, outletID)
	if err == nil && existing != nil {
		return nil, fmt.Errorf("account with number %s already exists", req.Number)
	}

	// Validate chart of account exists
	_, err = p.chartOfAccountRepo.GetByID(ctx, req.ChartOfAccountID)
	if err != nil {
		return nil, fmt.Errorf("chart of account not found: %w", err)
	}

	entity := mappers.AccountCreateRequestToEntity(req, organizationID, outletID)
	err = p.accountRepo.Create(ctx, entity)
	if err != nil {
		return nil, fmt.Errorf("failed to create account: %w", err)
	}

	return mappers.AccountEntityToResponse(entity), nil
}

func (p *AccountProcessorImpl) GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error) {
	entity, err := p.accountRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("account not found: %w", err)
	}

	return mappers.AccountEntityToResponse(entity), nil
}

func (p *AccountProcessorImpl) UpdateAccount(ctx context.Context, id uuid.UUID, req *models.UpdateAccountRequest) (*models.AccountResponse, error) {
	entity, err := p.accountRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("account not found: %w", err)
	}

	// Check if new number already exists (if number is being updated)
	if req.Number != nil && *req.Number != entity.Number {
		existing, err := p.accountRepo.GetByNumber(ctx, entity.OrganizationID, *req.Number, entity.OutletID)
		if err == nil && existing != nil {
			return nil, fmt.Errorf("account with number %s already exists", *req.Number)
		}
	}

	// Validate chart of account exists if provided
	if req.ChartOfAccountID != nil {
		_, err = p.chartOfAccountRepo.GetByID(ctx, *req.ChartOfAccountID)
		if err != nil {
			return nil, fmt.Errorf("chart of account not found: %w", err)
		}
	}

	mappers.AccountUpdateRequestToEntity(entity, req)
	err = p.accountRepo.Update(ctx, entity)
	if err != nil {
		return nil, fmt.Errorf("failed to update account: %w", err)
	}

	return mappers.AccountEntityToResponse(entity), nil
}

func (p *AccountProcessorImpl) DeleteAccount(ctx context.Context, id uuid.UUID) error {
	entity, err := p.accountRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("account not found: %w", err)
	}

	// Prevent deletion of system accounts
	if entity.IsSystem {
		return fmt.Errorf("cannot delete system account")
	}

	err = p.accountRepo.Delete(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to delete account: %w", err)
	}

	return nil
}

func (p *AccountProcessorImpl) ListAccounts(ctx context.Context, req *models.ListAccountsRequest) ([]models.AccountResponse, int, error) {
	// Get organization and outlet from context
	appCtx := appcontext.FromGinContext(ctx)
	organizationID := appCtx.OrganizationID
	var outletID *uuid.UUID
	if appCtx.OutletID != uuid.Nil {
		outletID = &appCtx.OutletID
	}

	filterEntity := &entities.Account{
		OrganizationID:   organizationID,
		OutletID:         outletID,
		ChartOfAccountID: *req.ChartOfAccountID,
		AccountType:      entities.AccountType(*req.AccountType),
	}

	entities, total, err := p.accountRepo.List(ctx, filterEntity)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list accounts: %w", err)
	}

	responses := make([]models.AccountResponse, len(entities))
	for i, entity := range entities {
		responses[i] = *mappers.AccountEntityToResponse(entity)
	}

	return responses, total, nil
}

func (p *AccountProcessorImpl) GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.AccountResponse, error) {
	entities, err := p.accountRepo.GetByOrganization(ctx, organizationID, outletID)
	if err != nil {
		return nil, fmt.Errorf("failed to get accounts by organization: %w", err)
	}

	responses := make([]models.AccountResponse, len(entities))
	for i, entity := range entities {
		responses[i] = *mappers.AccountEntityToResponse(entity)
	}

	return responses, nil
}

func (p *AccountProcessorImpl) GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]models.AccountResponse, error) {
	entities, err := p.accountRepo.GetByChartOfAccount(ctx, chartOfAccountID)
	if err != nil {
		return nil, fmt.Errorf("failed to get accounts by chart of account: %w", err)
	}

	responses := make([]models.AccountResponse, len(entities))
	for i, entity := range entities {
		responses[i] = *mappers.AccountEntityToResponse(entity)
	}

	return responses, nil
}

func (p *AccountProcessorImpl) UpdateAccountBalance(ctx context.Context, id uuid.UUID, amount float64) error {
	_, err := p.accountRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("account not found: %w", err)
	}

	err = p.accountRepo.UpdateBalance(ctx, id, amount)
	if err != nil {
		return fmt.Errorf("failed to update account balance: %w", err)
	}

	return nil
}

func (p *AccountProcessorImpl) GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error) {
	balance, err := p.accountRepo.GetBalance(ctx, id)
	if err != nil {
		return 0, fmt.Errorf("failed to get account balance: %w", err)
	}

	return balance, nil
}
