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 ChartOfAccountProcessor interface {
	CreateChartOfAccount(ctx context.Context, req *models.CreateChartOfAccountRequest) (*models.ChartOfAccountResponse, error)
	GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountResponse, error)
	UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountRequest) (*models.ChartOfAccountResponse, error)
	DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error
	ListChartOfAccounts(ctx context.Context, req *models.ListChartOfAccountsRequest) ([]models.ChartOfAccountResponse, int, error)
	GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error)
	GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error)
	CreateDefaultChartOfAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) error
}

type ChartOfAccountProcessorImpl struct {
	chartOfAccountRepo     ChartOfAccountRepository
	chartOfAccountTypeRepo ChartOfAccountTypeRepository
}

func NewChartOfAccountProcessorImpl(chartOfAccountRepo ChartOfAccountRepository, chartOfAccountTypeRepo ChartOfAccountTypeRepository) *ChartOfAccountProcessorImpl {
	return &ChartOfAccountProcessorImpl{
		chartOfAccountRepo:     chartOfAccountRepo,
		chartOfAccountTypeRepo: chartOfAccountTypeRepo,
	}
}

func (p *ChartOfAccountProcessorImpl) CreateChartOfAccount(ctx context.Context, req *models.CreateChartOfAccountRequest) (*models.ChartOfAccountResponse, 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 code already exists for this organization/outlet
	existing, err := p.chartOfAccountRepo.GetByCode(ctx, organizationID, req.Code, outletID)
	if err == nil && existing != nil {
		return nil, fmt.Errorf("chart of account with code %s already exists", req.Code)
	}

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

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

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

	return mappers.ChartOfAccountEntityToResponse(entity), nil
}

func (p *ChartOfAccountProcessorImpl) GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountResponse, error) {
	entity, err := p.chartOfAccountRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("chart of account not found: %w", err)
	}

	return mappers.ChartOfAccountEntityToResponse(entity), nil
}

func (p *ChartOfAccountProcessorImpl) UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountRequest) (*models.ChartOfAccountResponse, error) {
	entity, err := p.chartOfAccountRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("chart of account not found: %w", err)
	}

	// Check if new code already exists (if code is being updated)
	if req.Code != nil && *req.Code != entity.Code {
		existing, err := p.chartOfAccountRepo.GetByCode(ctx, entity.OrganizationID, *req.Code, &entity.OutletID)
		if err == nil && existing != nil {
			return nil, fmt.Errorf("chart of account with code %s already exists", *req.Code)
		}
	}

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

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

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

	return mappers.ChartOfAccountEntityToResponse(entity), nil
}

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

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

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

	return nil
}

func (p *ChartOfAccountProcessorImpl) ListChartOfAccounts(ctx context.Context, req *models.ListChartOfAccountsRequest) ([]models.ChartOfAccountResponse, int, error) {
	filterEntity := &entities.ChartOfAccount{
		OrganizationID: req.OrganizationID,
		OutletID:       req.OutletID,
		ParentID:       req.ParentID,
	}

	// Apply optional filter for ChartOfAccountTypeID
	if req.ChartOfAccountTypeID != nil {
		filterEntity.ChartOfAccountTypeID = *req.ChartOfAccountTypeID
	}

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

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

	return responses, total, nil
}

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

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

	return responses, nil
}

func (p *ChartOfAccountProcessorImpl) GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) {
	entities, err := p.chartOfAccountRepo.GetByType(ctx, organizationID, chartOfAccountTypeID, outletID)
	if err != nil {
		return nil, fmt.Errorf("failed to get chart of accounts by type: %w", err)
	}

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

	return responses, nil
}

func (p *ChartOfAccountProcessorImpl) CreateDefaultChartOfAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) error {
	// This method will be implemented to create default chart of accounts
	// based on the JSON template when a new organization/outlet is created
	// For now, we'll return nil as this is a placeholder
	return nil
}
