package service

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"time"

	"github.com/calendarapi/internal/auth"
	"github.com/calendarapi/internal/models"
	"github.com/calendarapi/internal/repository"
	"github.com/calendarapi/internal/utils"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"golang.org/x/crypto/bcrypt"
)

type AuthService struct {
	pool    *pgxpool.Pool
	queries *repository.Queries
	jwt     *auth.JWTManager
	audit   *AuditService
}

func NewAuthService(pool *pgxpool.Pool, queries *repository.Queries, jwt *auth.JWTManager, audit *AuditService) *AuthService {
	return &AuthService{pool: pool, queries: queries, jwt: jwt, audit: audit}
}

func (s *AuthService) Register(ctx context.Context, email, password, timezone string) (*models.AuthTokens, error) {
	email = utils.NormalizeEmail(email)
	if err := utils.ValidateEmail(email); err != nil {
		return nil, err
	}
	if err := utils.ValidatePassword(password); err != nil {
		return nil, err
	}
	if timezone == "" {
		timezone = "UTC"
	}
	if err := utils.ValidateTimezone(timezone); err != nil {
		return nil, err
	}

	_, err := s.queries.GetUserByEmail(ctx, email)
	if err == nil {
		return nil, models.NewConflictError("email already registered")
	}
	if err != pgx.ErrNoRows {
		return nil, models.ErrInternal
	}

	hash, err := bcrypt.GenerateFromPassword([]byte(password), 12)
	if err != nil {
		return nil, models.ErrInternal
	}

	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, models.ErrInternal
	}
	defer tx.Rollback(ctx)
	qtx := s.queries.WithTx(tx)

	userID := uuid.New()
	dbUser, err := qtx.CreateUser(ctx, repository.CreateUserParams{
		ID:           utils.ToPgUUID(userID),
		Email:        email,
		PasswordHash: string(hash),
		Timezone:     timezone,
	})
	if err != nil {
		return nil, models.ErrInternal
	}

	calID := uuid.New()
	_, err = qtx.CreateCalendar(ctx, repository.CreateCalendarParams{
		ID:       utils.ToPgUUID(calID),
		OwnerID:  utils.ToPgUUID(userID),
		Name:     "My Calendar",
		Color:    "#3B82F6",
		IsPublic: false,
	})
	if err != nil {
		return nil, models.ErrInternal
	}

	err = qtx.UpsertCalendarMember(ctx, repository.UpsertCalendarMemberParams{
		CalendarID: utils.ToPgUUID(calID),
		UserID:     utils.ToPgUUID(userID),
		Role:       "owner",
	})
	if err != nil {
		return nil, models.ErrInternal
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, models.ErrInternal
	}

	accessToken, err := s.jwt.GenerateAccessToken(userID)
	if err != nil {
		return nil, models.ErrInternal
	}

	refreshToken, err := s.jwt.GenerateRefreshToken(userID)
	if err != nil {
		return nil, models.ErrInternal
	}

	rtHash := hashToken(refreshToken)
	_, err = s.queries.CreateRefreshToken(ctx, repository.CreateRefreshTokenParams{
		ID:        utils.ToPgUUID(uuid.New()),
		UserID:    utils.ToPgUUID(userID),
		TokenHash: rtHash,
		ExpiresAt: utils.ToPgTimestamptz(time.Now().Add(auth.RefreshTokenDuration)),
	})
	if err != nil {
		return nil, models.ErrInternal
	}

	user := userFromCreateRow(dbUser)
	return &models.AuthTokens{User: user, AccessToken: accessToken, RefreshToken: refreshToken}, nil
}

func (s *AuthService) Login(ctx context.Context, email, password string) (*models.AuthTokens, error) {
	email = utils.NormalizeEmail(email)

	dbUser, err := s.queries.GetUserByEmail(ctx, email)
	if err != nil {
		if err == pgx.ErrNoRows {
			return nil, models.ErrAuthInvalid
		}
		return nil, models.ErrInternal
	}

	if err := bcrypt.CompareHashAndPassword([]byte(dbUser.PasswordHash), []byte(password)); err != nil {
		return nil, models.ErrAuthInvalid
	}

	userID := utils.FromPgUUID(dbUser.ID)
	accessToken, err := s.jwt.GenerateAccessToken(userID)
	if err != nil {
		return nil, models.ErrInternal
	}

	refreshToken, err := s.jwt.GenerateRefreshToken(userID)
	if err != nil {
		return nil, models.ErrInternal
	}

	rtHash := hashToken(refreshToken)
	_, err = s.queries.CreateRefreshToken(ctx, repository.CreateRefreshTokenParams{
		ID:        utils.ToPgUUID(uuid.New()),
		UserID:    utils.ToPgUUID(userID),
		TokenHash: rtHash,
		ExpiresAt: utils.ToPgTimestamptz(time.Now().Add(auth.RefreshTokenDuration)),
	})
	if err != nil {
		return nil, models.ErrInternal
	}

	user := userFromEmailRow(dbUser)
	return &models.AuthTokens{User: user, AccessToken: accessToken, RefreshToken: refreshToken}, nil
}

func (s *AuthService) Refresh(ctx context.Context, refreshTokenStr string) (*models.TokenPair, error) {
	rtHash := hashToken(refreshTokenStr)
	rt, err := s.queries.GetRefreshTokenByHash(ctx, rtHash)
	if err != nil {
		if err == pgx.ErrNoRows {
			return nil, models.ErrAuthInvalid
		}
		return nil, models.ErrInternal
	}

	if utils.FromPgTimestamptz(rt.ExpiresAt).Before(time.Now()) {
		return nil, models.ErrAuthInvalid
	}

	_ = s.queries.RevokeRefreshToken(ctx, rtHash)

	userID := utils.FromPgUUID(rt.UserID)
	accessToken, err := s.jwt.GenerateAccessToken(userID)
	if err != nil {
		return nil, models.ErrInternal
	}

	newRefresh, err := s.jwt.GenerateRefreshToken(userID)
	if err != nil {
		return nil, models.ErrInternal
	}

	newHash := hashToken(newRefresh)
	_, err = s.queries.CreateRefreshToken(ctx, repository.CreateRefreshTokenParams{
		ID:        utils.ToPgUUID(uuid.New()),
		UserID:    utils.ToPgUUID(userID),
		TokenHash: newHash,
		ExpiresAt: utils.ToPgTimestamptz(time.Now().Add(auth.RefreshTokenDuration)),
	})
	if err != nil {
		return nil, models.ErrInternal
	}

	return &models.TokenPair{AccessToken: accessToken, RefreshToken: newRefresh}, nil
}

func (s *AuthService) Logout(ctx context.Context, refreshTokenStr string) error {
	rtHash := hashToken(refreshTokenStr)
	return s.queries.RevokeRefreshToken(ctx, rtHash)
}

func hashToken(token string) string {
	h := sha256.Sum256([]byte(token))
	return hex.EncodeToString(h[:])
}

func userFromCreateRow(u repository.CreateUserRow) models.User {
	return models.User{
		ID:                          utils.FromPgUUID(u.ID),
		Email:                       u.Email,
		Timezone:                    u.Timezone,
		WeekStartDay:                0,
		DateFormat:                  "MM/dd/yyyy",
		TimeFormat:                  "12h",
		DefaultEventDurationMinutes: 60,
		DefaultReminderMinutes:      10,
		ShowWeekends:                true,
		WorkingHoursStart:           "09:00",
		WorkingHoursEnd:             "17:00",
		NotificationsEmail:          true,
		CreatedAt:                   utils.FromPgTimestamptz(u.CreatedAt),
		UpdatedAt:                   utils.FromPgTimestamptz(u.UpdatedAt),
	}
}

func userFromEmailRow(u repository.GetUserByEmailRow) models.User {
	return models.User{
		ID:                          utils.FromPgUUID(u.ID),
		Email:                       u.Email,
		Timezone:                    u.Timezone,
		WeekStartDay:                int(u.WeekStartDay),
		DateFormat:                  u.DateFormat,
		TimeFormat:                  u.TimeFormat,
		DefaultEventDurationMinutes: int(u.DefaultEventDurationMinutes),
		DefaultReminderMinutes:      int(u.DefaultReminderMinutes),
		ShowWeekends:                u.ShowWeekends,
		WorkingHoursStart:           u.WorkingHoursStart,
		WorkingHoursEnd:             u.WorkingHoursEnd,
		NotificationsEmail:          u.NotificationsEmail,
		CreatedAt:                   utils.FromPgTimestamptz(u.CreatedAt),
		UpdatedAt:                   utils.FromPgTimestamptz(u.UpdatedAt),
	}
}

func userFromIDRow(u repository.GetUserByIDRow) models.User {
	return models.User{
		ID:                          utils.FromPgUUID(u.ID),
		Email:                       u.Email,
		Timezone:                    u.Timezone,
		WeekStartDay:                int(u.WeekStartDay),
		DateFormat:                  u.DateFormat,
		TimeFormat:                  u.TimeFormat,
		DefaultEventDurationMinutes: int(u.DefaultEventDurationMinutes),
		DefaultReminderMinutes:      int(u.DefaultReminderMinutes),
		ShowWeekends:                u.ShowWeekends,
		WorkingHoursStart:           u.WorkingHoursStart,
		WorkingHoursEnd:             u.WorkingHoursEnd,
		NotificationsEmail:          u.NotificationsEmail,
		CreatedAt:                   utils.FromPgTimestamptz(u.CreatedAt),
		UpdatedAt:                   utils.FromPgTimestamptz(u.UpdatedAt),
	}
}

func userFromUpdateRow(u repository.UpdateUserRow) models.User {
	return models.User{
		ID:                          utils.FromPgUUID(u.ID),
		Email:                       u.Email,
		Timezone:                    u.Timezone,
		WeekStartDay:                int(u.WeekStartDay),
		DateFormat:                  u.DateFormat,
		TimeFormat:                  u.TimeFormat,
		DefaultEventDurationMinutes: int(u.DefaultEventDurationMinutes),
		DefaultReminderMinutes:      int(u.DefaultReminderMinutes),
		ShowWeekends:                u.ShowWeekends,
		WorkingHoursStart:           u.WorkingHoursStart,
		WorkingHoursEnd:             u.WorkingHoursEnd,
		NotificationsEmail:          u.NotificationsEmail,
		CreatedAt:                   utils.FromPgTimestamptz(u.CreatedAt),
		UpdatedAt:                   utils.FromPgTimestamptz(u.UpdatedAt),
	}
}
