package processor

import (
	"context"
	"fmt"

	"golang.org/x/crypto/bcrypt"

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

	"github.com/google/uuid"
)

type OrganizationProcessor interface {
	CreateOrganization(ctx context.Context, req *models.CreateOrganizationRequest) (*models.CreateOrganizationResponse, error)
	UpdateOrganization(ctx context.Context, id uuid.UUID, req *models.UpdateOrganizationRequest) (*models.OrganizationResponse, error)
	DeleteOrganization(ctx context.Context, id uuid.UUID) error
	GetOrganizationByID(ctx context.Context, id uuid.UUID) (*models.OrganizationResponse, error)
	ListOrganizations(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.OrganizationResponse, int, error)
}

type OrganizationProcessorImpl struct {
	organizationRepo OrganizationRepository
	outletRepo       OutletRepository
	userRepo         UserRepository
}

func NewOrganizationProcessorImpl(
	organizationRepo OrganizationRepository,
	outletRepo OutletRepository,
	userRepo UserRepository,
) *OrganizationProcessorImpl {
	return &OrganizationProcessorImpl{
		organizationRepo: organizationRepo,
		outletRepo:       outletRepo,
		userRepo:         userRepo,
	}
}

func (p *OrganizationProcessorImpl) CreateOrganization(ctx context.Context, req *models.CreateOrganizationRequest) (*models.CreateOrganizationResponse, error) {
	if req.OrganizationEmail != nil && *req.OrganizationEmail != "" {
		existingOrg, err := p.organizationRepo.GetByEmail(ctx, *req.OrganizationEmail)
		if err == nil && existingOrg != nil {
			return nil, fmt.Errorf("organization with email %s already exists", *req.OrganizationEmail)
		}
	}

	existingUser, err := p.userRepo.GetByEmail(ctx, req.AdminEmail)
	if err == nil && existingUser != nil {
		return nil, fmt.Errorf("user with email %s already exists", req.AdminEmail)
	}

	organizationEntity := &entities.Organization{
		Name:        req.OrganizationName,
		Email:       req.OrganizationEmail,
		PhoneNumber: req.OrganizationPhoneNumber,
		PlanType:    string(req.PlanType),
	}

	err = p.organizationRepo.Create(ctx, organizationEntity)
	if err != nil {
		return nil, fmt.Errorf("failed to create organization: %w", err)
	}

	passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.AdminPassword), bcrypt.DefaultCost)
	if err != nil {
		return nil, fmt.Errorf("failed to hash password: %w", err)
	}

	adminUserEntity := &entities.User{
		OrganizationID: organizationEntity.ID,
		Name:           req.AdminName,
		Email:          req.AdminEmail,
		PasswordHash:   string(passwordHash),
		Role:           entities.RoleAdmin,
		IsActive:       true,
	}

	err = p.userRepo.Create(ctx, adminUserEntity)
	if err != nil {
		return nil, fmt.Errorf("failed to create admin user: %w", err)
	}

	defaultOutletEntity := &entities.Outlet{
		OrganizationID: organizationEntity.ID,
		Name:           req.OutletName,
		Address:        req.OutletAddress,
		Timezone:       req.OutletTimezone,
		Currency:       req.OutletCurrency,
		TaxRate:        0.0,
		IsActive:       true,
	}

	err = p.outletRepo.Create(ctx, defaultOutletEntity)
	if err != nil {
		return nil, fmt.Errorf("failed to create default outlet: %w", err)
	}

	organizationResponse := mappers.OrganizationEntityToResponse(organizationEntity)
	adminUserResponse := mappers.UserEntityToResponse(adminUserEntity)
	outletResponse := mappers.OutletEntityToResponse(defaultOutletEntity)

	return &models.CreateOrganizationResponse{
		Organization:  organizationResponse,
		AdminUser:     adminUserResponse,
		DefaultOutlet: outletResponse,
	}, nil
}

func (p *OrganizationProcessorImpl) UpdateOrganization(ctx context.Context, id uuid.UUID, req *models.UpdateOrganizationRequest) (*models.OrganizationResponse, error) {
	existingOrg, err := p.organizationRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("organization not found: %w", err)
	}

	if req.Email != nil && (existingOrg.Email == nil || *req.Email != *existingOrg.Email) {
		existingOrgByEmail, err := p.organizationRepo.GetByEmail(ctx, *req.Email)
		if err == nil && existingOrgByEmail != nil && existingOrgByEmail.ID != id {
			return nil, fmt.Errorf("organization with email %s already exists", *req.Email)
		}
	}

	if req.Name != nil {
		existingOrg.Name = *req.Name
	}
	if req.Email != nil {
		existingOrg.Email = req.Email
	}
	if req.PhoneNumber != nil {
		existingOrg.PhoneNumber = req.PhoneNumber
	}
	if req.PlanType != nil {
		existingOrg.PlanType = string(*req.PlanType)
	}

	err = p.organizationRepo.Update(ctx, existingOrg)
	if err != nil {
		return nil, fmt.Errorf("failed to update organization: %w", err)
	}

	return mappers.OrganizationEntityToResponse(existingOrg), nil
}

func (p *OrganizationProcessorImpl) DeleteOrganization(ctx context.Context, id uuid.UUID) error {
	_, err := p.organizationRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("organization not found: %w", err)
	}

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

	return nil
}

func (p *OrganizationProcessorImpl) GetOrganizationByID(ctx context.Context, id uuid.UUID) (*models.OrganizationResponse, error) {
	organization, err := p.organizationRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("organization not found: %w", err)
	}

	return mappers.OrganizationEntityToResponse(organization), nil
}

func (p *OrganizationProcessorImpl) ListOrganizations(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.OrganizationResponse, int, error) {
	offset := (page - 1) * limit

	organizations, totalCount, err := p.organizationRepo.List(ctx, filters, limit, offset)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to get organizations: %w", err)
	}

	responses := make([]models.OrganizationResponse, len(organizations))
	for i, org := range organizations {
		response := mappers.OrganizationEntityToResponse(org)
		if response != nil {
			responses[i] = *response
		}
	}

	return responses, int(totalCount), nil
}
