package auth

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

	"photum-backend/internal/config"
	"photum-backend/internal/db/generated"
	"photum-backend/internal/profissionais"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
	"golang.org/x/crypto/bcrypt"
)

const (
	RoleSuperAdmin    = "SUPERADMIN"
	RoleBusinessOwner = "BUSINESS_OWNER"
	RolePhotographer  = "PHOTOGRAPHER"
	RoleEventOwner    = "EVENT_OWNER"
	RoleAgendaViewer  = "AGENDA_VIEWER"
	RoleResearcher    = "RESEARCHER"
)

type Service struct {
	queries              *generated.Queries
	profissionaisService *profissionais.Service
	jwtAccessSecret      string
	jwtRefreshSecret     string
	jwtAccessTTLMinutes  int
	jwtRefreshTTLDays    int
}

func NewService(queries *generated.Queries, profissionaisService *profissionais.Service, cfg *config.Config) *Service {
	return &Service{
		queries:              queries,
		profissionaisService: profissionaisService,
		jwtAccessSecret:      cfg.JwtAccessSecret,
		jwtRefreshSecret:     cfg.JwtRefreshSecret,
		jwtAccessTTLMinutes:  cfg.JwtAccessTTLMinutes,
		jwtRefreshTTLDays:    cfg.JwtRefreshTTLDays,
	}
}

func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefone, tipoProfissional string, empresaID *string, profissionalData *profissionais.CreateProfissionalInput, regiao, cpfCnpj, cep, endereco, numero, complemento, bairro, cidade, estado string) (*generated.Usuario, error) {
	// Hash password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	// Create user
	user, err := s.queries.CreateUsuario(ctx, generated.CreateUsuarioParams{
		Email:            email,
		SenhaHash:        string(hashedPassword),
		Role:             role,
		TipoProfissional: toPgText(&tipoProfissional),
		Ativo:            false,
	})
	if err != nil {
		return nil, err
	}

	// If region is provided and different from default (or just force set it)
	// We want to ensure the user has access to the region they registered in.
	if regiao != "" {
		err = s.queries.UpdateUsuarioRegions(ctx, generated.UpdateUsuarioRegionsParams{
			ID:                pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
			RegioesPermitidas: []string{regiao}, // Set strictly to the selected region
		})
		if err != nil {
			_ = s.queries.DeleteUsuario(ctx, user.ID)
			return nil, err
		}
		// Update user object in memory to reflect this (for Login call later if it uses this object?
		// Login fetches fresh from DB, so it's fine)
	}

	// If role is 'PHOTOGRAPHER' or 'BUSINESS_OWNER', create professional profile
	if (role == RolePhotographer || role == RoleBusinessOwner) && profissionalData != nil {
		userID := uuid.UUID(user.ID.Bytes).String()
		_, err := s.profissionaisService.Create(ctx, userID, *profissionalData, regiao)
		if err != nil {
			// Rollback user creation (best effort)
			_ = s.queries.DeleteUsuario(ctx, user.ID)
			return nil, err
		}
	}

	// If role is 'EVENT_OWNER', create client profile
	if role == RoleEventOwner {
		var empID pgtype.UUID
		if empresaID != nil && *empresaID != "" {
			parsedEmpID, err := uuid.Parse(*empresaID)
			if err == nil {
				empID = pgtype.UUID{Bytes: parsedEmpID, Valid: true}
			}
		}

		_, err = s.queries.CreateCadastroCliente(ctx, generated.CreateCadastroClienteParams{
			UsuarioID:   pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
			EmpresaID:   empID,
			Nome:        pgtype.Text{String: nome, Valid: true},
			Telefone:    pgtype.Text{String: telefone, Valid: telefone != ""},
			CpfCnpj:     toPgText(&cpfCnpj),
			Cep:         toPgText(&cep),
			Endereco:    toPgText(&endereco),
			Numero:      toPgText(&numero),
			Complemento: toPgText(&complemento),
			Bairro:      toPgText(&bairro),
			Cidade:      toPgText(&cidade),
			Estado:      toPgText(&estado),
		})
		if err != nil {
			_ = s.queries.DeleteUsuario(ctx, user.ID)
			return nil, err
		}
	}

	return &user, nil
}

type TokenPair struct {
	AccessToken  string
	RefreshToken string
}

func (s *Service) Login(ctx context.Context, email, senha string) (*TokenPair, *generated.GetUsuarioByEmailRow, *generated.GetProfissionalByUsuarioIDRow, error) {
	// The query now returns a Row with joined fields, not just Usuario struct
	user, err := s.queries.GetUsuarioByEmail(ctx, email)
	if err != nil {
		fmt.Printf("[DEBUG] Login Failed: Email %s not found. Error: %v\n", email, err)
		return nil, nil, nil, errors.New("invalid credentials")
	}

	err = bcrypt.CompareHashAndPassword([]byte(user.SenhaHash), []byte(senha))
	if err != nil {
		fmt.Printf("[DEBUG] Login Failed: Password mismatch for %s. DBHash: %s... Input: %s. Error: %v\n", email, user.SenhaHash[:10], senha, err)
		return nil, nil, nil, errors.New("invalid credentials")
	}

	userUUID := uuid.UUID(user.ID.Bytes)
	accessToken, _, err := GenerateAccessToken(userUUID, user.Role, user.RegioesPermitidas, s.jwtAccessSecret, s.jwtAccessTTLMinutes)
	if err != nil {
		return nil, nil, nil, err
	}

	refreshToken, err := GenerateRefreshToken(userUUID, s.jwtRefreshSecret, s.jwtRefreshTTLDays)
	if err != nil {
		return nil, nil, nil, err
	}

	var profData *generated.GetProfissionalByUsuarioIDRow
	if user.Role == RolePhotographer || user.Role == RoleBusinessOwner {
		p, err := s.queries.GetProfissionalByUsuarioID(ctx, user.ID)
		if err == nil {
			profData = &p
		}
	}

	return &TokenPair{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
	}, &user, profData, nil
}

func (s *Service) Refresh(ctx context.Context, refreshTokenRaw string) (string, time.Time, error) {
	hash := sha256.Sum256([]byte(refreshTokenRaw))
	hashString := hex.EncodeToString(hash[:])

	storedToken, err := s.queries.GetRefreshToken(ctx, hashString)
	if err != nil {
		return "", time.Time{}, errors.New("invalid refresh token")
	}

	if storedToken.Revogado {
		return "", time.Time{}, errors.New("token revoked")
	}

	if time.Now().After(storedToken.ExpiraEm.Time) {
		return "", time.Time{}, errors.New("token expired")
	}

	user, err := s.queries.GetUsuarioByID(ctx, storedToken.UsuarioID)
	if err != nil {
		return "", time.Time{}, errors.New("user not found")
	}

	userUUID := uuid.UUID(user.ID.Bytes)
	return GenerateAccessToken(userUUID, user.Role, user.RegioesPermitidas, s.jwtAccessSecret, s.jwtAccessTTLMinutes)
}

func (s *Service) Logout(ctx context.Context, refreshTokenRaw string) error {
	hash := sha256.Sum256([]byte(refreshTokenRaw))
	hashString := hex.EncodeToString(hash[:])
	return s.queries.RevokeRefreshToken(ctx, hashString)
}

func (s *Service) ListPendingUsers(ctx context.Context, regiao string) ([]generated.ListUsuariosPendingRow, error) {
	if regiao == "" {
		regiao = "SP"
	}
	return s.queries.ListUsuariosPending(ctx, regiao)
}

func (s *Service) ApproveUser(ctx context.Context, id string) error {
	parsedUUID, err := uuid.Parse(id)
	if err != nil {
		return err
	}

	var pgID pgtype.UUID
	pgID.Bytes = parsedUUID
	pgID.Valid = true

	_, err = s.queries.UpdateUsuarioAtivo(ctx, generated.UpdateUsuarioAtivoParams{
		ID:    pgID,
		Ativo: true,
	})
	return err
}

func (s *Service) AdminCreateUser(ctx context.Context, email, senha, role, nome, tipoProfissional string, ativo bool, regiao string, empresaID string, telefone, cpfCnpj, cep, endereco, numero, complemento, bairro, cidade, estado string) (*generated.Usuario, error) {
	// Hash password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	// Create user
	user, err := s.queries.CreateUsuario(ctx, generated.CreateUsuarioParams{
		Email:            email,
		SenhaHash:        string(hashedPassword),
		Role:             role,
		TipoProfissional: toPgText(&tipoProfissional),
	})
	if err != nil {
		return nil, err
	}

	// Update Regions if provided
	if regiao != "" {
		err = s.queries.UpdateUsuarioRegions(ctx, generated.UpdateUsuarioRegionsParams{
			ID:                pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
			RegioesPermitidas: []string{regiao},
		})
		if err != nil {
			_ = s.queries.DeleteUsuario(ctx, user.ID)
			return nil, err
		}
	}

	if ativo {
		// Approve user immediately
		err = s.ApproveUser(ctx, uuid.UUID(user.ID.Bytes).String())
		if err != nil {
			_ = s.queries.DeleteUsuario(ctx, user.ID)
			return nil, err
		}
		user.Ativo = true
	}

	// Link Company if EVENT_OWNER
	if role == RoleEventOwner && empresaID != "" {
		empUuid, err := uuid.Parse(empresaID)
		if err == nil {
			_, err = s.queries.CreateCadastroCliente(ctx, generated.CreateCadastroClienteParams{
				UsuarioID:   pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
				EmpresaID:   pgtype.UUID{Bytes: empUuid, Valid: true},
				Nome:        pgtype.Text{String: nome, Valid: true},
				Telefone:    toPgText(&telefone),
				CpfCnpj:     toPgText(&cpfCnpj),
				Cep:         toPgText(&cep),
				Endereco:    toPgText(&endereco),
				Numero:      toPgText(&numero),
				Complemento: toPgText(&complemento),
				Bairro:      toPgText(&bairro),
				Cidade:      toPgText(&cidade),
				Estado:      toPgText(&estado),
			})
			if err != nil {
				// Log error but maybe don't fail user creation?
				// Ideally rollback
				_ = s.queries.DeleteUsuario(ctx, user.ID)
				return nil, err
			}
		}
	}

	// Create professional profile if applicable
	if role == RolePhotographer || role == RoleBusinessOwner {
		var funcaoID string

		// If specific professional type is provided, resolve it
		if tipoProfissional != "" {
			// Resolve Function ID by Name using list scanning (since GetByName doesn't exist)
			funcoes, err := s.queries.ListFuncoes(ctx)
			if err == nil {
				for _, f := range funcoes {
					if f.Nome == tipoProfissional {
						funcaoID = uuid.UUID(f.ID.Bytes).String()
						break
					}
				}
			}
		}

		// Prepare professional input
		profInput := profissionais.CreateProfissionalInput{
			Nome:                 nome,
			Email:                &email,
			FuncaoProfissionalID: funcaoID,
			Whatsapp:             &telefone,
			CpfCnpjTitular:       &cpfCnpj,
			Cep:                  &cep,
			Endereco:             &endereco,
			Numero:               &numero,
			Complemento:          &complemento,
			Bairro:               &bairro,
			Cidade:               &cidade,
			Uf:                   &estado,
		}

		// Create the professional
		_, err = s.profissionaisService.Create(ctx, uuid.UUID(user.ID.Bytes).String(), profInput, regiao)
		if err != nil {
			// Try to delete user to rollback
			_ = s.queries.DeleteUsuario(ctx, user.ID)
			return nil, err
		}
	}

	return &user, nil
}

func (s *Service) UpdateUserRole(ctx context.Context, id, newRole string) error {
	parsedUUID, err := uuid.Parse(id)
	if err != nil {
		return err
	}
	var pgID pgtype.UUID
	pgID.Bytes = parsedUUID
	pgID.Valid = true

	_, err = s.queries.UpdateUsuarioRole(ctx, generated.UpdateUsuarioRoleParams{
		ID:   pgID,
		Role: newRole,
	})
	return err
}

func (s *Service) DeleteUser(ctx context.Context, id string) error {
	parsedUUID, err := uuid.Parse(id)
	if err != nil {
		return err
	}
	var pgID pgtype.UUID
	pgID.Bytes = parsedUUID
	pgID.Valid = true

	return s.queries.DeleteUsuario(ctx, pgID)
}

func (s *Service) EnsureDemoUsers(ctx context.Context) error {
	demoUsers := []struct {
		Email   string
		Role    string
		Name    string
		Regions []string // Optional specific regions
	}{
		{"admin@photum.com", RoleSuperAdmin, "Dev Admin", []string{"SP", "MG"}},
		{"empresa@photum.com", RoleBusinessOwner, "PHOTUM CEO", []string{"SP", "MG"}},
		{"admin.sp@photum.com", RoleBusinessOwner, "ADMIN SP", []string{"SP"}},
		{"admin.mg@photum.com", RoleBusinessOwner, "ADMIN MG", []string{"MG"}},
		{"foto@photum.com", RolePhotographer, "COLABORADOR PHOTUM", []string{"SP"}},
		{"cliente@photum.com", RoleEventOwner, "CLIENTE TESTE", []string{"SP"}},
		{"pesquisa@photum.com", RoleResearcher, "PESQUISADOR", []string{"SP"}},
		{"viewer@photum.com", RoleAgendaViewer, "VISUALIZADOR PHOTUM", []string{"SP", "MG"}},
	}

	for _, u := range demoUsers {
		existingUser, err := s.queries.GetUsuarioByEmail(ctx, u.Email)

		regions := u.Regions
		if len(regions) == 0 {
			regions = []string{"SP"} // Default fallback
		}

		hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)

		if err != nil {
			fmt.Printf("[DEBUG] Creating User %s\n", u.Email)
			// Create if not exists
			if errors.Is(err, pgx.ErrNoRows) { // Only create if user not found
				fmt.Printf("Creating demo user: %s (%s)\n", u.Email, u.Role)
				// Pass empty strings for new client fields for demo users
				user, err := s.AdminCreateUser(ctx, u.Email, "123456", u.Role, u.Name, "", true, regions[0], "", "", "", "", "", "", "", "", "", "")
				if err != nil {
					fmt.Printf("Error creating demo user %s: %v\n", u.Email, err)
					return err
				}
				// Update to include specific regions
				err = s.queries.UpdateUsuarioRegions(ctx, generated.UpdateUsuarioRegionsParams{
					ID:                pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
					RegioesPermitidas: regions,
				})
				if err != nil {
					fmt.Printf("[DEBUG] Error updating regions for new user %s: %v\n", u.Email, err)
					return err
				}
			} else { // If it's another error, return it
				return err
			}
		} else {
			// User exists, FORCE update password and ensure regions
			fmt.Printf("[DEBUG] Updating Existing User %s\n", u.Email)

			pgID := pgtype.UUID{Bytes: existingUser.ID.Bytes, Valid: true}

			// Update Password
			err = s.queries.UpdateUsuarioSenha(ctx, generated.UpdateUsuarioSenhaParams{
				ID:        pgID,
				SenhaHash: string(hashedPassword),
			})
			if err != nil {
				fmt.Printf("[DEBUG] Error updating password for %s: %v\n", u.Email, err)
				return err
			}

			// Update Regions
			err = s.queries.UpdateUsuarioRegions(ctx, generated.UpdateUsuarioRegionsParams{
				ID:                pgID,
				RegioesPermitidas: regions,
			})
			if err != nil {
				fmt.Printf("[DEBUG] Error updating regions for %s: %v\n", u.Email, err)
				return err
			}

			// Update Role if mismatch
			if existingUser.Role != u.Role {
				fmt.Printf("[DEBUG] Updating Role from %s to %s for %s\n", existingUser.Role, u.Role, u.Email)
				err = s.UpdateUserRole(ctx, uuid.UUID(existingUser.ID.Bytes).String(), u.Role)
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

type UpdateClientInput struct {
	Nome        string
	Telefone    string
	CpfCnpj     string
	Cep         string
	Endereco    string
	Numero      string
	Complemento string
	Bairro      string
	Cidade      string
	Estado      string
}

func (s *Service) UpdateClientData(ctx context.Context, userID string, input UpdateClientInput) error {
	uid, err := uuid.Parse(userID)
	if err != nil {
		return err
	}

	// Prepare params
	params := generated.UpdateCadastroClienteParams{
		UsuarioID:   pgtype.UUID{Bytes: uid, Valid: true},
		Nome:        pgtype.Text{String: input.Nome, Valid: input.Nome != ""},
		Telefone:    pgtype.Text{String: input.Telefone, Valid: input.Telefone != ""},
		CpfCnpj:     pgtype.Text{String: input.CpfCnpj, Valid: input.CpfCnpj != ""},
		Cep:         pgtype.Text{String: input.Cep, Valid: input.Cep != ""},
		Endereco:    pgtype.Text{String: input.Endereco, Valid: input.Endereco != ""},
		Numero:      pgtype.Text{String: input.Numero, Valid: input.Numero != ""},
		Complemento: pgtype.Text{String: input.Complemento, Valid: input.Complemento != ""},
		Bairro:      pgtype.Text{String: input.Bairro, Valid: input.Bairro != ""},
		Cidade:      pgtype.Text{String: input.Cidade, Valid: input.Cidade != ""},
		Estado:      pgtype.Text{String: input.Estado, Valid: input.Estado != ""},
	}

	_, err = s.queries.UpdateCadastroCliente(ctx, params)
	return err
}

func (s *Service) ListUsers(ctx context.Context, regiao string) ([]generated.ListAllUsuariosRow, error) {
	return s.queries.ListAllUsuarios(ctx, regiao)
}

func (s *Service) GetUser(ctx context.Context, id string) (*generated.GetUsuarioByIDRow, error) {
	parsedUUID, err := uuid.Parse(id)
	if err != nil {
		return nil, err
	}
	var pgID pgtype.UUID
	pgID.Bytes = parsedUUID
	pgID.Valid = true

	user, err := s.queries.GetUsuarioByID(ctx, pgID)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

func (s *Service) GetProfessionalByUserID(ctx context.Context, userID string) (*generated.GetProfissionalByUsuarioIDRow, error) {
	parsedUUID, err := uuid.Parse(userID)
	if err != nil {
		return nil, err
	}
	var pgID pgtype.UUID
	pgID.Bytes = parsedUUID
	pgID.Valid = true

	p, err := s.queries.GetProfissionalByUsuarioID(ctx, pgID)
	if err != nil {
		return nil, err
	}
	return &p, nil
}

func (s *Service) GetClientData(ctx context.Context, userID string) (*generated.CadastroCliente, error) {
	parsedUUID, err := uuid.Parse(userID)
	if err != nil {
		return nil, err
	}
	var pgID pgtype.UUID
	pgID.Bytes = parsedUUID
	pgID.Valid = true

	c, err := s.queries.GetCadastroClienteByUsuarioID(ctx, pgID)
	if err != nil {
		return nil, err
	}
	return &c, nil
}

func toPgText(s *string) pgtype.Text {
	if s == nil {
		return pgtype.Text{Valid: false}
	}
	return pgtype.Text{String: *s, Valid: true}
}
