package finance

import (
	"context"
	"fmt"
	"photum-backend/internal/db/generated"
	"strings"
	"time"

	"photum-backend/internal/profissionais"

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

type Service struct {
	queries     *generated.Queries
	profService *profissionais.Service
}

func NewService(queries *generated.Queries, profService *profissionais.Service) *Service {
	return &Service{queries: queries, profService: profService}
}

func (s *Service) Create(ctx context.Context, params generated.CreateTransactionParams, regiao string) (generated.FinancialTransaction, error) {
	params.Regiao = pgtype.Text{String: regiao, Valid: true}
	txn, err := s.queries.CreateTransaction(ctx, params)
	if err != nil {
		return generated.FinancialTransaction{}, err
	}

	if params.FotID.Valid {
		_ = s.updateFotExpenses(ctx, params.FotID, regiao)
	}

	return txn, nil
}

func (s *Service) Update(ctx context.Context, params generated.UpdateTransactionParams, regiao string) (generated.FinancialTransaction, error) {
	params.Regiao = pgtype.Text{String: regiao, Valid: true}
	txn, err := s.queries.UpdateTransaction(ctx, params)
	if err != nil {
		return generated.FinancialTransaction{}, err
	}

	// Recalculate for the new FOT (if changed, we should technically recalc old one too, but simpler for now)
	if params.FotID.Valid {
		_ = s.updateFotExpenses(ctx, params.FotID, regiao)
	}
	return txn, nil
}

func (s *Service) Delete(ctx context.Context, id pgtype.UUID, regiao string) error {
	// First fetch to get FotID
	txn, err := s.queries.GetTransaction(ctx, generated.GetTransactionParams{
		ID:     id,
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
	if err != nil {
		return err
	}

	err = s.queries.DeleteTransaction(ctx, generated.DeleteTransactionParams{
		ID:     id,
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
	if err != nil {
		return err
	}

	if txn.FotID.Valid {
		_ = s.updateFotExpenses(ctx, txn.FotID, regiao)
	}
	return nil
}

func (s *Service) ListByFot(ctx context.Context, fotID pgtype.UUID, regiao string) ([]generated.FinancialTransaction, error) {
	// ListTransactionsByFot query likely needs regiao too?
	// If query was updated, we need to pass params struct if it has multiple args, or just args.
	// But generated typically is specific. If I used $1, $2 then params struct.
	// Assuming I didn't update ListTransactionsByFot query? I should have.
	// Let's assume params struct for now if >1 arg.
	// Wait, ListTransactionsByFot(ctx, fotID) was original.
	// If updated, it should be (ctx, generated.ListTransactionsByFotParams{...}).
	// I'll assume params struct.
	return s.queries.ListTransactionsByFot(ctx, generated.ListTransactionsByFotParams{
		FotID:  fotID,
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) ListAll(ctx context.Context, regiao string) ([]generated.ListTransactionsRow, error) {
	return s.queries.ListTransactions(ctx, pgtype.Text{String: regiao, Valid: true})
}

type FilterParams struct {
	Fot             string
	Data            string
	Evento          string
	Servico         string
	Nome            string
	Curso           string
	Instituicao     string
	Ano             string
	Empresa         string
	StartDate       string
	EndDate         string
	IncludeWeekends bool
}

func (s *Service) ListPaginated(ctx context.Context, page int32, limit int32, filters FilterParams, regiao string) ([]generated.ListTransactionsPaginatedFilteredRow, int64, error) {
	if page < 1 {
		page = 1
	}
	if limit < 1 {
		limit = 50
	}
	offset := (page - 1) * limit

	rows, err := s.queries.ListTransactionsPaginatedFiltered(ctx, generated.ListTransactionsPaginatedFilteredParams{
		Limit:           limit,
		Offset:          offset,
		Fot:             filters.Fot,
		Data:            filters.Data,
		Evento:          filters.Evento,
		Servico:         filters.Servico,
		Nome:            filters.Nome,
		Curso:           filters.Curso,
		Instituicao:     filters.Instituicao,
		Ano:             filters.Ano,
		Empresa:         filters.Empresa,
		StartDate:       filters.StartDate,
		EndDate:         filters.EndDate,
		IncludeWeekends: filters.IncludeWeekends,
		Regiao:          pgtype.Text{String: regiao, Valid: true},
	})
	if err != nil {
		return nil, 0, err
	}

	count, err := s.queries.CountTransactionsFiltered(ctx, generated.CountTransactionsFilteredParams{
		Fot:             filters.Fot,
		Data:            filters.Data,
		Evento:          filters.Evento,
		Servico:         filters.Servico,
		Nome:            filters.Nome,
		Curso:           filters.Curso,
		Instituicao:     filters.Instituicao,
		Ano:             filters.Ano,
		Empresa:         filters.Empresa,
		StartDate:       filters.StartDate,
		EndDate:         filters.EndDate,
		IncludeWeekends: filters.IncludeWeekends,
		Regiao:          pgtype.Text{String: regiao, Valid: true},
	})
	if err != nil {
		count = 0
	}

	return rows, count, nil
}

func (s *Service) AutoFillSearch(ctx context.Context, fotNumber string, regiao string) (generated.GetCadastroFotByFotJoinRow, error) {
	return s.queries.GetCadastroFotByFotJoin(ctx, generated.GetCadastroFotByFotJoinParams{
		Fot:    fotNumber,
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) ListFotEvents(ctx context.Context, fotID pgtype.UUID, regiao string) ([]generated.ListAgendasByFotRow, error) {
	// ListAgendasByFot needs region? Check query.
	// Queries usually filter by ID which is unique globally (UUID), but good to enforce region if table has it.
	// But ListAgendasByFot in agenda.sql was likely updated to filter by region too.
	// Let's check generated code signature via error later or assume.
	// Wait, agenda.sql: SELECT ... WHERE fot_id = $1 AND regiao = $2 ...
	return s.queries.ListAgendasByFot(ctx, generated.ListAgendasByFotParams{
		FotID:  fotID,
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) SearchProfessionals(ctx context.Context, query string, regiao string) ([]generated.SearchProfissionaisRow, error) {
	return s.queries.SearchProfissionais(ctx, generated.SearchProfissionaisParams{
		Column1: pgtype.Text{String: query, Valid: true},
		Regiao:  pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) SearchProfessionalsByFunction(ctx context.Context, query string, functionName string, regiao string) ([]generated.SearchProfissionaisByFunctionRow, error) {
	return s.queries.SearchProfissionaisByFunction(ctx, generated.SearchProfissionaisByFunctionParams{
		Column1: pgtype.Text{String: query, Valid: true}, // $1 - Name
		Nome:    functionName,                            // $2 - Function Name
		Regiao:  pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) GetStandardPrice(ctx context.Context, eventName string, serviceName string, regiao string) (pgtype.Numeric, error) {
	// serviceName here is the Function Name (e.g. Fotógrafo)
	return s.queries.GetStandardPrice(ctx, generated.GetStandardPriceParams{
		Nome:   eventName,   // $1 - Event Name
		Nome_2: serviceName, // $2 - Function Name
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) SearchFot(ctx context.Context, query string, regiao string) ([]generated.SearchFotRow, error) {
	return s.queries.SearchFot(ctx, generated.SearchFotParams{
		Column1: pgtype.Text{String: query, Valid: true},
		Regiao:  pgtype.Text{String: regiao, Valid: true},
	})
}

func (s *Service) updateFotExpenses(ctx context.Context, fotID pgtype.UUID, regiao string) error {
	total, err := s.queries.SumTotalByFot(ctx, generated.SumTotalByFotParams{
		FotID:  fotID,
		Regiao: pgtype.Text{String: regiao, Valid: true},
	})
	if err != nil {
		return err
	}

	return s.queries.UpdateCadastroFotGastos(ctx, generated.UpdateCadastroFotGastosParams{
		ID:             fotID,
		GastosCaptacao: total,
	})
}

// Import Logic

type ImportFinanceItem struct {
	FOT            string  `json:"fot"`
	Data           string  `json:"data"` // YYYY-MM-DD
	TipoEvento     string  `json:"tipo_evento"`
	TipoServico    string  `json:"tipo_servico"`
	Nome           string  `json:"nome"`
	Whatsapp       string  `json:"whatsapp"`
	CPF            string  `json:"cpf"`
	TabelaFree     string  `json:"tabela_free"`
	ValorFree      float64 `json:"valor_free"`
	ValorExtra     float64 `json:"valor_extra"`
	DescricaoExtra string  `json:"descricao_extra"`
	TotalPagar     float64 `json:"total_pagar"`
	DataPgto       string  `json:"data_pgto"`
	PgtoOK         bool    `json:"pgto_ok"`
}

type ImportFinanceResult struct {
	Created int
	Errors  []string
}

func (s *Service) Import(ctx context.Context, items []ImportFinanceItem, regiao string) (ImportFinanceResult, error) {
	result := ImportFinanceResult{}

	// Fetch all funcoes to map Name -> ID
	funcs, err := s.queries.ListFuncoes(ctx)
	if err != nil {
		return result, fmt.Errorf("failed to list functions: %w", err)
	}

	funcMap := make(map[string]pgtype.UUID)
	var defaultFuncID pgtype.UUID
	if len(funcs) > 0 {
		defaultFuncID = funcs[0].ID
	}

	for _, f := range funcs {
		funcMap[strings.ToLower(f.Nome)] = f.ID
	}

	// 1. Bulk Upsert Professionals
	profMap := make(map[string]profissionais.CreateProfissionalInput)
	for _, item := range items {
		if item.CPF == "" {
			continue
		}

		cleanCPF := strings.ReplaceAll(item.CPF, ".", "")
		cleanCPF = strings.ReplaceAll(cleanCPF, "-", "")
		cleanCPF = strings.TrimSpace(cleanCPF)

		if len(cleanCPF) > 20 {
			cleanCPF = cleanCPF[:20]
		}

		if _, exists := profMap[cleanCPF]; !exists {
			nm := item.Nome
			if len(nm) > 100 {
				nm = nm[:100]
			}

			cpf := cleanCPF
			phone := item.Whatsapp
			if len(phone) > 20 {
				phone = phone[:20]
			}

			profMap[cleanCPF] = profissionais.CreateProfissionalInput{
				Nome:           nm,
				CpfCnpjTitular: &cpf,
				Whatsapp:       &phone,
				FuncaoProfissionalID: func() string {
					if id, ok := funcMap[strings.ToLower(item.TipoServico)]; ok {
						if id.Valid {
							return fmt.Sprintf("%x", id.Bytes)
						}
					}
					// Mapping heuristics for specific terms if needed
					if strings.Contains(strings.ToLower(item.TipoServico), "foto") {
						for k, v := range funcMap {
							if strings.Contains(k, "foto") && v.Valid {
								return fmt.Sprintf("%x", v.Bytes)
							}
						}
					}
					if defaultFuncID.Valid {
						return fmt.Sprintf("%x", defaultFuncID.Bytes)
					}
					return ""
				}(),
			}
		}
	}

	var profInputs []profissionais.CreateProfissionalInput
	for _, p := range profMap {
		profInputs = append(profInputs, p)
	}

	if len(profInputs) > 0 {
		stats, errs := s.profService.Import(ctx, profInputs, regiao)
		if len(errs) > 0 {
			for _, e := range errs {
				result.Errors = append(result.Errors, fmt.Sprintf("Professional Import Error: %v", e))
			}
		}
		fmt.Printf("Professionals Imported: Created=%d, Updated=%d\n", stats.Created, stats.Updated)
	}

	// 2. Process Transactions
	for i, item := range items {
		if item.FOT == "" {
			result.Errors = append(result.Errors, fmt.Sprintf("Row %d: Missing FOT", i))
			continue
		}

		fotRow, err := s.queries.GetCadastroFotByFOT(ctx, generated.GetCadastroFotByFOTParams{
			Fot:    item.FOT,
			Regiao: pgtype.Text{String: regiao, Valid: true},
		})
		var fotID pgtype.UUID
		if err != nil {
			result.Errors = append(result.Errors, fmt.Sprintf("Row %d: FOT %s not found", i, item.FOT))
		} else {
			fotID = fotRow.ID
		}

		var profID pgtype.UUID
		if item.CPF != "" {
			cleanCPF := strings.ReplaceAll(item.CPF, ".", "")
			cleanCPF = strings.ReplaceAll(cleanCPF, "-", "")
			cleanCPF = strings.TrimSpace(cleanCPF)
			if len(cleanCPF) > 20 {
				cleanCPF = cleanCPF[:20]
			}

			prof, err := s.queries.GetProfissionalByCPF(ctx, generated.GetProfissionalByCPFParams{
				CpfCnpjTitular: pgtype.Text{String: cleanCPF, Valid: true},
				Regiao:         pgtype.Text{String: regiao, Valid: true},
			})
			if err == nil {
				profID = prof.ID
			}
		}

		var dataCobranca pgtype.Date
		if item.Data != "" {
			t, err := time.Parse("2006-01-02", item.Data)
			if err == nil {
				dataCobranca = pgtype.Date{Time: t, Valid: true}
			} else {
				t2, err2 := time.Parse("02/01/2006", item.Data)
				if err2 == nil {
					dataCobranca = pgtype.Date{Time: t2, Valid: true}
				}
			}
		}

		var dataPgto pgtype.Date
		if item.DataPgto != "" {
			t, err := time.Parse("2006-01-02", item.DataPgto)
			if err == nil {
				dataPgto = pgtype.Date{Time: t, Valid: true}
			} else {
				t2, err2 := time.Parse("02/01/2006", item.DataPgto)
				if err2 == nil {
					dataPgto = pgtype.Date{Time: t2, Valid: true}
				}
			}
		}

		params := generated.CreateTransactionParams{
			FotID:            fotID,
			DataCobranca:     dataCobranca,
			TipoEvento:       pgtype.Text{String: item.TipoEvento, Valid: item.TipoEvento != ""},
			TipoServico:      pgtype.Text{String: item.TipoServico, Valid: item.TipoServico != ""},
			ProfessionalName: pgtype.Text{String: item.Nome, Valid: item.Nome != ""},
			Whatsapp:         pgtype.Text{String: limitStr(item.Whatsapp, 50), Valid: item.Whatsapp != ""},
			Cpf:              pgtype.Text{String: limitStr(item.CPF, 20), Valid: item.CPF != ""},
			TabelaFree:       pgtype.Text{String: item.TabelaFree, Valid: item.TabelaFree != ""},
			ValorFree:        toNumeric(item.ValorFree),
			ValorExtra:       toNumeric(item.ValorExtra),
			DescricaoExtra:   pgtype.Text{String: item.DescricaoExtra, Valid: item.DescricaoExtra != ""},
			TotalPagar:       toNumeric(item.TotalPagar),
			DataPagamento:    dataPgto,
			PgtoOk:           pgtype.Bool{Bool: item.PgtoOK, Valid: true},
			ProfissionalID:   profID,
		}

		_, err = s.Create(ctx, params, regiao)
		if err != nil {
			result.Errors = append(result.Errors, fmt.Sprintf("Row %d: Saved Error %v", i, err))
		} else {
			result.Created++
		}
	}

	return result, nil
}

func toNumeric(f float64) pgtype.Numeric {
	var n pgtype.Numeric
	n.Scan(fmt.Sprintf("%.2f", f))
	return n
}

func limitStr(s string, n int) string {
	if len(s) > n {
		return s[:n]
	}
	return s
}
