package tenancy

import (
	"context"
	"errors"
	"regexp"
	"strings"
	"time"

	"bookra/apps/backend/internal/db"
	"bookra/apps/backend/internal/domain"
	"bookra/apps/backend/internal/shared"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
)

var (
	ErrTenantAlreadyProvisioned = errors.New("tenant already provisioned for user")
	ErrInvalidOnboarding        = errors.New("invalid onboarding request")
	ErrTenantSlugTaken          = errors.New("tenant slug is already in use")
)

var tenantSlugPattern = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)

type Service struct {
	repo db.Repository
}

func NewService(repo db.Repository) *Service {
	return &Service{repo: repo}
}

func (s *Service) GetTenantMembership(ctx context.Context, principal domain.Principal) (db.TenantMembershipRecord, error) {
	return s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
}

func (s *Service) Bootstrap(ctx context.Context, principal domain.Principal) (domain.TenantBootstrap, error) {
	membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return domain.TenantBootstrap{
				TenantID:   "",
				TenantName: "",
				Preset:     "",
				Locale:     "cs",
				Timezone:   "Europe/Prague",
				CurrentUser: domain.Principal{
					Subject: principal.Subject,
					Email:   principal.Email,
					Name:    principal.Name,
					Role:    principal.Role,
				},
			}, nil
		}
		return domain.TenantBootstrap{}, err
	}

	return domain.TenantBootstrap{
		TenantID:            membership.Tenant.ID,
		TenantName:          membership.Tenant.Name,
		TenantSlug:          membership.Tenant.Slug,
		Preset:              membership.Tenant.Preset,
		Locale:              membership.Tenant.Locale,
		Timezone:            membership.Tenant.Timezone,
		PlanCode:            shared.NormalizePlanCode(membership.Tenant.PlanCode),
		OnboardingCompleted: true,
		Brand:               s.brandProfile(ctx, membership.Tenant),
		CurrentUser: domain.Principal{
			Subject: principal.Subject,
			Email:   principal.Email,
			Name:    principal.Name,
			Role:    membership.Role,
		},
	}, nil
}

func (s *Service) Onboard(ctx context.Context, principal domain.Principal, request domain.OnboardTenantRequest) (domain.TenantBootstrap, error) {
	if _, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject); err == nil {
		return domain.TenantBootstrap{}, ErrTenantAlreadyProvisioned
	} else if !errors.Is(err, pgx.ErrNoRows) {
		return domain.TenantBootstrap{}, err
	}

	name := strings.TrimSpace(request.Name)
	slug := strings.TrimSpace(request.Slug)
	preset := strings.TrimSpace(request.Preset)
	locale := strings.TrimSpace(request.Locale)
	timezone := strings.TrimSpace(request.Timezone)
	locationName := strings.TrimSpace(request.LocationName)
	brand := request.Brand
	if strings.TrimSpace(brand.Name) == "" {
		brand.Name = name
	}
	defaults := request.BookingDefaults
	if strings.TrimSpace(defaults.ServiceName) == "" {
		defaults.ServiceName = "First appointment"
	}
	if defaults.DurationMinutes == 0 {
		defaults.DurationMinutes = 60
	}
	if defaults.CancelWindowHours == 0 {
		defaults.CancelWindowHours = 24
	}

	switch {
	case len(name) < 2 || len(name) > 80:
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case len(slug) < 3 || len(slug) > 48 || !tenantSlugPattern.MatchString(slug):
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case preset != "salon" && preset != "clinic" && preset != "massage" && preset != "repair" && preset != "studio":
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case locale != "cs" && locale != "en":
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case timezone == "":
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case len(locationName) > 120:
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case defaults.DurationMinutes < 15 || defaults.DurationMinutes > 480:
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case defaults.BufferBeforeMinutes < 0 || defaults.BufferBeforeMinutes > 180:
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case defaults.BufferAfterMinutes < 0 || defaults.BufferAfterMinutes > 180:
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	case defaults.CancelWindowHours < 1 || defaults.CancelWindowHours > 720:
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	}
	if _, err := time.LoadLocation(timezone); err != nil {
		return domain.TenantBootstrap{}, ErrInvalidOnboarding
	}
	if err := validateAvailabilityBlocks(request.AvailabilityBlocks); err != nil {
		return domain.TenantBootstrap{}, err
	}

	membership, err := s.repo.CreateTenantForUser(ctx, db.CreateTenantForUserParams{
		Subject:             principal.Subject,
		Name:                name,
		Slug:                slug,
		Preset:              preset,
		Locale:              locale,
		Timezone:            timezone,
		BrandName:           strings.TrimSpace(brand.Name),
		SiteURL:             strings.TrimSpace(brand.SiteURL),
		LogoURL:             strings.TrimSpace(brand.LogoURL),
		PrimaryColor:        strings.TrimSpace(brand.PrimaryColor),
		LocationName:        locationName,
		ServiceName:         strings.TrimSpace(defaults.ServiceName),
		DurationMinutes:     defaults.DurationMinutes,
		BufferBeforeMinutes: defaults.BufferBeforeMinutes,
		BufferAfterMinutes:  defaults.BufferAfterMinutes,
		CancelWindowHours:   defaults.CancelWindowHours,
		AvailabilityBlocks:  toAvailabilityBlocks(request.AvailabilityBlocks),
		TeamInvites:         toTeamInvites(request.TeamInvites),
	})
	if err != nil {
		var pgErr *pgconn.PgError
		if errors.As(err, &pgErr) && pgErr.Code == "23505" {
			return domain.TenantBootstrap{}, ErrTenantSlugTaken
		}
		return domain.TenantBootstrap{}, err
	}

	return domain.TenantBootstrap{
		TenantID:            membership.Tenant.ID,
		TenantName:          membership.Tenant.Name,
		TenantSlug:          membership.Tenant.Slug,
		Preset:              membership.Tenant.Preset,
		Locale:              membership.Tenant.Locale,
		Timezone:            membership.Tenant.Timezone,
		PlanCode:            shared.NormalizePlanCode(membership.Tenant.PlanCode),
		OnboardingCompleted: true,
		Brand: domain.BrandProfile{
			Name:         strings.TrimSpace(brand.Name),
			SiteURL:      strings.TrimSpace(brand.SiteURL),
			LogoURL:      strings.TrimSpace(brand.LogoURL),
			PrimaryColor: strings.TrimSpace(brand.PrimaryColor),
		},
		CurrentUser: domain.Principal{
			Subject: principal.Subject,
			Email:   principal.Email,
			Name:    principal.Name,
			Role:    membership.Role,
		},
	}, nil
}

func (s *Service) brandProfile(ctx context.Context, tenant db.TenantRecord) domain.BrandProfile {
	brand, err := s.repo.GetBrandProfile(ctx, tenant.ID)
	if err != nil {
		return domain.BrandProfile{Name: tenant.Name}
	}
	return domain.BrandProfile{
		Name:         firstNonEmpty(brand.Name, tenant.Name),
		SiteURL:      brand.SiteURL,
		LogoURL:      brand.LogoURL,
		PrimaryColor: brand.PrimaryColor,
	}
}

func validateAvailabilityBlocks(blocks []domain.AvailabilityBlockRequest) error {
	for _, block := range blocks {
		if block.DayOfWeek < 0 || block.DayOfWeek > 6 {
			return ErrInvalidOnboarding
		}
		starts, err := time.Parse("15:04", block.StartsLocal)
		if err != nil {
			starts, err = time.Parse("15:04:05", block.StartsLocal)
		}
		if err != nil {
			return ErrInvalidOnboarding
		}
		ends, err := time.Parse("15:04", block.EndsLocal)
		if err != nil {
			ends, err = time.Parse("15:04:05", block.EndsLocal)
		}
		if err != nil || !ends.After(starts) {
			return ErrInvalidOnboarding
		}
	}
	return nil
}

func toAvailabilityBlocks(blocks []domain.AvailabilityBlockRequest) []db.AvailabilityBlockRecord {
	records := make([]db.AvailabilityBlockRecord, 0, len(blocks))
	for _, block := range blocks {
		records = append(records, db.AvailabilityBlockRecord{
			DayOfWeek:   block.DayOfWeek,
			StartsLocal: normalizeClock(block.StartsLocal),
			EndsLocal:   normalizeClock(block.EndsLocal),
			Busy:        block.Busy,
		})
	}
	return records
}

func toTeamInvites(invites []domain.TeamInviteRequest) []db.TeamInviteRecord {
	records := make([]db.TeamInviteRecord, 0, len(invites))
	for _, invite := range invites {
		email := strings.TrimSpace(strings.ToLower(invite.Email))
		if email == "" {
			continue
		}
		role := strings.TrimSpace(invite.Role)
		if role == "" {
			role = "staff"
		}
		records = append(records, db.TeamInviteRecord{Email: email, Role: role})
	}
	return records
}

func normalizeClock(value string) string {
	value = strings.TrimSpace(value)
	if len(value) == len("15:04") {
		return value + ":00"
	}
	return value
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}
