package repository

import (
	"context"
	"encoding/json"
	"errors"
	"time"

	dbgen "Yimaru-Backend/gen/db"
	"Yimaru-Backend/internal/domain"
	"Yimaru-Backend/internal/ports"

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

func NewTeamStore(s *Store) ports.TeamStore { return s }

func (s *Store) CreateTeamMember(ctx context.Context, member domain.TeamMember) (domain.TeamMember, error) {
	var permissionsJSON []byte
	if len(member.Permissions) > 0 {
		var err error
		permissionsJSON, err = json.Marshal(member.Permissions)
		if err != nil {
			return domain.TeamMember{}, err
		}
	}

	var hireDate pgtype.Date
	if member.HireDate != nil {
		hireDate = pgtype.Date{Time: *member.HireDate, Valid: true}
	}

	var createdBy pgtype.Int8
	if member.CreatedBy != nil {
		createdBy = pgtype.Int8{Int64: *member.CreatedBy, Valid: true}
	}

	res, err := s.queries.CreateTeamMember(ctx, dbgen.CreateTeamMemberParams{
		FirstName:         member.FirstName,
		LastName:          member.LastName,
		Email:             member.Email,
		PhoneNumber:       pgtype.Text{String: member.PhoneNumber, Valid: member.PhoneNumber != ""},
		Password:          member.Password,
		TeamRole:          string(member.TeamRole),
		Department:        pgtype.Text{String: member.Department, Valid: member.Department != ""},
		JobTitle:          pgtype.Text{String: member.JobTitle, Valid: member.JobTitle != ""},
		EmploymentType:    pgtype.Text{String: string(member.EmploymentType), Valid: member.EmploymentType != ""},
		HireDate:          hireDate,
		ProfilePictureUrl: pgtype.Text{String: member.ProfilePictureURL, Valid: member.ProfilePictureURL != ""},
		Bio:               pgtype.Text{String: member.Bio, Valid: member.Bio != ""},
		WorkPhone:         pgtype.Text{String: member.WorkPhone, Valid: member.WorkPhone != ""},
		EmergencyContact:  pgtype.Text{String: member.EmergencyContact, Valid: member.EmergencyContact != ""},
		Status:            string(member.Status),
		EmailVerified:     member.EmailVerified,
		Permissions:       permissionsJSON,
		CreatedBy:         createdBy,
	})
	if err != nil {
		return domain.TeamMember{}, err
	}

	return mapDBTeamMember(res), nil
}

func (s *Store) GetTeamMemberByID(ctx context.Context, id int64) (domain.TeamMember, error) {
	res, err := s.queries.GetTeamMemberByID(ctx, id)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return domain.TeamMember{}, domain.ErrTeamMemberNotFound
		}
		return domain.TeamMember{}, err
	}
	return mapDBTeamMember(res), nil
}

func (s *Store) GetTeamMemberByEmail(ctx context.Context, email string) (domain.TeamMember, error) {
	res, err := s.queries.GetTeamMemberByEmail(ctx, email)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return domain.TeamMember{}, domain.ErrTeamMemberNotFound
		}
		return domain.TeamMember{}, err
	}
	return mapDBTeamMember(res), nil
}

func (s *Store) GetAllTeamMembers(
	ctx context.Context,
	teamRole, department, status *string,
	limit, offset int32,
) ([]domain.TeamMember, int64, error) {
	var teamRoleParam, departmentParam, statusParam pgtype.Text
	if teamRole != nil {
		teamRoleParam = pgtype.Text{String: *teamRole, Valid: true}
	}
	if department != nil {
		departmentParam = pgtype.Text{String: *department, Valid: true}
	}
	if status != nil {
		statusParam = pgtype.Text{String: *status, Valid: true}
	}

	rows, err := s.queries.GetAllTeamMembers(ctx, dbgen.GetAllTeamMembersParams{
		TeamRole:   teamRoleParam,
		Department: departmentParam,
		Status:     statusParam,
		Limit:      pgtype.Int4{Int32: limit, Valid: true},
		Offset:     pgtype.Int4{Int32: offset, Valid: true},
	})
	if err != nil {
		return nil, 0, err
	}

	if len(rows) == 0 {
		return []domain.TeamMember{}, 0, nil
	}

	var totalCount int64
	members := make([]domain.TeamMember, len(rows))
	for i, row := range rows {
		if i == 0 {
			totalCount = row.TotalCount
		}
		members[i] = mapGetAllTeamMembersRow(row)
	}

	return members, totalCount, nil
}

func (s *Store) SearchTeamMembers(
	ctx context.Context,
	search string,
	teamRole, status *string,
) ([]domain.TeamMember, error) {
	var teamRoleParam, statusParam pgtype.Text
	if teamRole != nil {
		teamRoleParam = pgtype.Text{String: *teamRole, Valid: true}
	}
	if status != nil {
		statusParam = pgtype.Text{String: *status, Valid: true}
	}

	rows, err := s.queries.SearchTeamMembers(ctx, dbgen.SearchTeamMembersParams{
		Column1:  pgtype.Text{String: search, Valid: true},
		TeamRole: teamRoleParam,
		Status:   statusParam,
	})
	if err != nil {
		return nil, err
	}

	members := make([]domain.TeamMember, len(rows))
	for i, row := range rows {
		members[i] = mapSearchTeamMembersRow(row)
	}

	return members, nil
}

func (s *Store) UpdateTeamMember(ctx context.Context, req domain.UpdateTeamMemberReq) error {
	var permissionsJSON []byte
	if len(req.Permissions) > 0 {
		var err error
		permissionsJSON, err = json.Marshal(req.Permissions)
		if err != nil {
			return err
		}
	}

	var hireDate pgtype.Date
	if req.HireDate != "" {
		t, err := parseDate(req.HireDate)
		if err != nil {
			return err
		}
		hireDate = pgtype.Date{Time: t, Valid: true}
	}

	return s.queries.UpdateTeamMember(ctx, dbgen.UpdateTeamMemberParams{
		FirstName:         req.FirstName,
		LastName:          req.LastName,
		PhoneNumber:       pgtype.Text{String: req.PhoneNumber, Valid: req.PhoneNumber != ""},
		TeamRole:          req.TeamRole,
		Department:        pgtype.Text{String: req.Department, Valid: req.Department != ""},
		JobTitle:          pgtype.Text{String: req.JobTitle, Valid: req.JobTitle != ""},
		EmploymentType:    pgtype.Text{String: req.EmploymentType, Valid: req.EmploymentType != ""},
		HireDate:          hireDate,
		ProfilePictureUrl: pgtype.Text{String: req.ProfilePictureURL, Valid: req.ProfilePictureURL != ""},
		Bio:               pgtype.Text{String: req.Bio, Valid: req.Bio != ""},
		WorkPhone:         pgtype.Text{String: req.WorkPhone, Valid: req.WorkPhone != ""},
		EmergencyContact:  pgtype.Text{String: req.EmergencyContact, Valid: req.EmergencyContact != ""},
		Permissions:       permissionsJSON,
		UpdatedBy:         pgtype.Int8{Int64: req.UpdatedBy, Valid: req.UpdatedBy > 0},
		ID:                req.TeamMemberID,
	})
}

func (s *Store) UpdateTeamMemberStatus(ctx context.Context, req domain.UpdateTeamMemberStatusReq) error {
	return s.queries.UpdateTeamMemberStatus(ctx, dbgen.UpdateTeamMemberStatusParams{
		Status:    req.Status,
		UpdatedBy: pgtype.Int8{Int64: req.UpdatedBy, Valid: req.UpdatedBy > 0},
		ID:        req.TeamMemberID,
	})
}

func (s *Store) UpdateTeamMemberPassword(ctx context.Context, memberID int64, password string) error {
	return s.queries.UpdateTeamMemberPassword(ctx, dbgen.UpdateTeamMemberPasswordParams{
		Password: []byte(password),
		ID:       memberID,
	})
}

func (s *Store) UpdateTeamMemberLastLogin(ctx context.Context, memberID int64) error {
	return s.queries.UpdateTeamMemberLastLogin(ctx, memberID)
}

func (s *Store) DeleteTeamMember(ctx context.Context, memberID int64) error {
	return s.queries.DeleteTeamMember(ctx, memberID)
}

func (s *Store) CheckTeamMemberEmailExists(ctx context.Context, email string) (bool, error) {
	return s.queries.CheckTeamMemberEmailExists(ctx, email)
}

func (s *Store) GetTeamMembersByDepartment(ctx context.Context, department string) ([]domain.TeamMember, error) {
	rows, err := s.queries.GetTeamMembersByDepartment(ctx, pgtype.Text{String: department, Valid: true})
	if err != nil {
		return nil, err
	}

	members := make([]domain.TeamMember, len(rows))
	for i, row := range rows {
		members[i] = mapGetTeamMembersByDepartmentRow(row)
	}

	return members, nil
}

func (s *Store) GetTeamMembersByRole(ctx context.Context, role string) ([]domain.TeamMember, error) {
	rows, err := s.queries.GetTeamMembersByRole(ctx, role)
	if err != nil {
		return nil, err
	}

	members := make([]domain.TeamMember, len(rows))
	for i, row := range rows {
		members[i] = mapGetTeamMembersByRoleRow(row)
	}

	return members, nil
}

func (s *Store) CountTeamMembersByStatus(ctx context.Context) (domain.TeamMemberStats, error) {
	res, err := s.queries.CountTeamMembersByStatus(ctx)
	if err != nil {
		return domain.TeamMemberStats{}, err
	}

	return domain.TeamMemberStats{
		ActiveCount:     res.ActiveCount,
		InactiveCount:   res.InactiveCount,
		SuspendedCount:  res.SuspendedCount,
		TerminatedCount: res.TerminatedCount,
		TotalCount:      res.TotalCount,
	}, nil
}

func (s *Store) UpdateTeamMemberEmailVerified(ctx context.Context, memberID int64, verified bool) error {
	return s.queries.UpdateTeamMemberEmailVerified(ctx, dbgen.UpdateTeamMemberEmailVerifiedParams{
		EmailVerified: verified,
		ID:            memberID,
	})
}

func mapDBTeamMember(m dbgen.TeamMember) domain.TeamMember {
	var permissions []string
	if len(m.Permissions) > 0 {
		_ = json.Unmarshal(m.Permissions, &permissions)
	}

	var hireDate *time.Time
	if m.HireDate.Valid {
		hireDate = &m.HireDate.Time
	}

	var lastLogin *time.Time
	if m.LastLogin.Valid {
		lastLogin = &m.LastLogin.Time
	}

	var createdBy *int64
	if m.CreatedBy.Valid {
		createdBy = &m.CreatedBy.Int64
	}

	var updatedBy *int64
	if m.UpdatedBy.Valid {
		updatedBy = &m.UpdatedBy.Int64
	}

	var updatedAt *time.Time
	if m.UpdatedAt.Valid {
		updatedAt = &m.UpdatedAt.Time
	}

	return domain.TeamMember{
		ID:                m.ID,
		FirstName:         m.FirstName,
		LastName:          m.LastName,
		Email:             m.Email,
		PhoneNumber:       m.PhoneNumber.String,
		Password:          m.Password,
		TeamRole:          domain.TeamRole(m.TeamRole),
		Department:        m.Department.String,
		JobTitle:          m.JobTitle.String,
		EmploymentType:    domain.EmploymentType(m.EmploymentType.String),
		HireDate:          hireDate,
		ProfilePictureURL: m.ProfilePictureUrl.String,
		Bio:               m.Bio.String,
		WorkPhone:         m.WorkPhone.String,
		EmergencyContact:  m.EmergencyContact.String,
		Status:            domain.TeamMemberStatus(m.Status),
		EmailVerified:     m.EmailVerified,
		Permissions:       permissions,
		LastLogin:         lastLogin,
		CreatedBy:         createdBy,
		UpdatedBy:         updatedBy,
		CreatedAt:         m.CreatedAt.Time,
		UpdatedAt:         updatedAt,
	}
}

func mapGetAllTeamMembersRow(row dbgen.GetAllTeamMembersRow) domain.TeamMember {
	var permissions []string
	if len(row.Permissions) > 0 {
		_ = json.Unmarshal(row.Permissions, &permissions)
	}

	var hireDate *time.Time
	if row.HireDate.Valid {
		hireDate = &row.HireDate.Time
	}

	var lastLogin *time.Time
	if row.LastLogin.Valid {
		lastLogin = &row.LastLogin.Time
	}

	var updatedAt *time.Time
	if row.UpdatedAt.Valid {
		updatedAt = &row.UpdatedAt.Time
	}

	return domain.TeamMember{
		ID:                row.ID,
		FirstName:         row.FirstName,
		LastName:          row.LastName,
		Email:             row.Email,
		PhoneNumber:       row.PhoneNumber.String,
		TeamRole:          domain.TeamRole(row.TeamRole),
		Department:        row.Department.String,
		JobTitle:          row.JobTitle.String,
		EmploymentType:    domain.EmploymentType(row.EmploymentType.String),
		HireDate:          hireDate,
		ProfilePictureURL: row.ProfilePictureUrl.String,
		Bio:               row.Bio.String,
		WorkPhone:         row.WorkPhone.String,
		Status:            domain.TeamMemberStatus(row.Status),
		EmailVerified:     row.EmailVerified,
		Permissions:       permissions,
		LastLogin:         lastLogin,
		CreatedAt:         row.CreatedAt.Time,
		UpdatedAt:         updatedAt,
	}
}

func mapSearchTeamMembersRow(row dbgen.SearchTeamMembersRow) domain.TeamMember {
	var permissions []string
	if len(row.Permissions) > 0 {
		_ = json.Unmarshal(row.Permissions, &permissions)
	}

	var hireDate *time.Time
	if row.HireDate.Valid {
		hireDate = &row.HireDate.Time
	}

	var lastLogin *time.Time
	if row.LastLogin.Valid {
		lastLogin = &row.LastLogin.Time
	}

	var updatedAt *time.Time
	if row.UpdatedAt.Valid {
		updatedAt = &row.UpdatedAt.Time
	}

	return domain.TeamMember{
		ID:                row.ID,
		FirstName:         row.FirstName,
		LastName:          row.LastName,
		Email:             row.Email,
		PhoneNumber:       row.PhoneNumber.String,
		TeamRole:          domain.TeamRole(row.TeamRole),
		Department:        row.Department.String,
		JobTitle:          row.JobTitle.String,
		EmploymentType:    domain.EmploymentType(row.EmploymentType.String),
		HireDate:          hireDate,
		ProfilePictureURL: row.ProfilePictureUrl.String,
		Bio:               row.Bio.String,
		Status:            domain.TeamMemberStatus(row.Status),
		EmailVerified:     row.EmailVerified,
		Permissions:       permissions,
		LastLogin:         lastLogin,
		CreatedAt:         row.CreatedAt.Time,
		UpdatedAt:         updatedAt,
	}
}

func mapGetTeamMembersByDepartmentRow(row dbgen.GetTeamMembersByDepartmentRow) domain.TeamMember {
	return domain.TeamMember{
		ID:                row.ID,
		FirstName:         row.FirstName,
		LastName:          row.LastName,
		Email:             row.Email,
		PhoneNumber:       row.PhoneNumber.String,
		TeamRole:          domain.TeamRole(row.TeamRole),
		Department:        row.Department.String,
		JobTitle:          row.JobTitle.String,
		EmploymentType:    domain.EmploymentType(row.EmploymentType.String),
		ProfilePictureURL: row.ProfilePictureUrl.String,
		Status:            domain.TeamMemberStatus(row.Status),
		CreatedAt:         row.CreatedAt.Time,
	}
}

func mapGetTeamMembersByRoleRow(row dbgen.GetTeamMembersByRoleRow) domain.TeamMember {
	return domain.TeamMember{
		ID:                row.ID,
		FirstName:         row.FirstName,
		LastName:          row.LastName,
		Email:             row.Email,
		PhoneNumber:       row.PhoneNumber.String,
		TeamRole:          domain.TeamRole(row.TeamRole),
		Department:        row.Department.String,
		JobTitle:          row.JobTitle.String,
		EmploymentType:    domain.EmploymentType(row.EmploymentType.String),
		ProfilePictureURL: row.ProfilePictureUrl.String,
		Status:            domain.TeamMemberStatus(row.Status),
		CreatedAt:         row.CreatedAt.Time,
	}
}

func parseDate(dateStr string) (time.Time, error) {
	return time.Parse("2006-01-02", dateStr)
}
