package server

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/gofrs/uuid/v5"
	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/jmoiron/sqlx"
	httpSwagger "github.com/swaggo/http-swagger"

	"github.com/saveinmed/backend-go/internal/config"
	"github.com/saveinmed/backend-go/internal/domain"
	"github.com/saveinmed/backend-go/internal/http/handler"
	"github.com/saveinmed/backend-go/internal/http/middleware"
	"github.com/saveinmed/backend-go/internal/payments"
	"github.com/saveinmed/backend-go/internal/repository/postgres"
	"github.com/saveinmed/backend-go/internal/usecase"
)

// Server wires the infrastructure and exposes HTTP handlers.
type Server struct {
	cfg config.Config
	db  *sqlx.DB
	mux *http.ServeMux
	svc *usecase.Service
}

func New(cfg config.Config) (*Server, error) {
	db, err := sqlx.Open("pgx", cfg.DatabaseURL)
	if err != nil {
		return nil, err
	}

	repo := postgres.New(db)
	gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission)
	svc := usecase.NewService(repo, gateway, cfg.MarketplaceCommission, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper)
	h := handler.New(svc)

	mux := http.NewServeMux()

	// Root endpoint
	mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			http.NotFound(w, r)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		response := `{"message":"💊 SaveInMed API is running!","docs":"/docs/index.html","health":"/health","version":"1.0.0"}`
		_, _ = w.Write([]byte(response))
	})

	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})

	auth := middleware.RequireAuth([]byte(cfg.JWTSecret))
	adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin")

	mux.Handle("POST /api/v1/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip))
	mux.Handle("GET /api/v1/companies", chain(http.HandlerFunc(h.ListCompanies), middleware.Logger, middleware.Gzip))
	mux.Handle("GET /api/v1/companies/{id}", chain(http.HandlerFunc(h.GetCompany), middleware.Logger, middleware.Gzip))
	mux.Handle("PATCH /api/v1/companies/{id}", chain(http.HandlerFunc(h.UpdateCompany), middleware.Logger, middleware.Gzip))
	mux.Handle("DELETE /api/v1/companies/{id}", chain(http.HandlerFunc(h.DeleteCompany), middleware.Logger, middleware.Gzip))
	mux.Handle("PATCH /api/v1/companies/{id}/verify", chain(http.HandlerFunc(h.VerifyCompany), middleware.Logger, middleware.Gzip, adminOnly))
	mux.Handle("GET /api/v1/companies/me", chain(http.HandlerFunc(h.GetMyCompany), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/companies/{id}/rating", chain(http.HandlerFunc(h.GetCompanyRating), middleware.Logger, middleware.Gzip))

	mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip))
	mux.Handle("GET /api/v1/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip))
	mux.Handle("GET /api/v1/products/search", chain(http.HandlerFunc(h.SearchProducts), middleware.Logger, middleware.Gzip, middleware.OptionalAuth([]byte(cfg.JWTSecret))))
	mux.Handle("GET /api/v1/products/{id}", chain(http.HandlerFunc(h.GetProduct), middleware.Logger, middleware.Gzip))
	mux.Handle("GET /api/v1/marketplace/records", chain(http.HandlerFunc(h.ListMarketplaceRecords), middleware.Logger, middleware.Gzip))

	mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip))
	mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip))

	mux.Handle("GET /api/v1/inventory", chain(http.HandlerFunc(h.ListInventory), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("POST /api/v1/inventory/adjust", chain(http.HandlerFunc(h.AdjustInventory), middleware.Logger, middleware.Gzip, auth))

	mux.Handle("POST /api/v1/orders", chain(http.HandlerFunc(h.CreateOrder), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/orders", chain(http.HandlerFunc(h.ListOrders), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/orders/{id}", chain(http.HandlerFunc(h.GetOrder), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("PATCH /api/v1/orders/{id}/status", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("DELETE /api/v1/orders/{id}", chain(http.HandlerFunc(h.DeleteOrder), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("POST /api/v1/orders/{id}/payment", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip, auth))

	mux.Handle("POST /api/v1/shipments", chain(http.HandlerFunc(h.CreateShipment), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/shipments", chain(http.HandlerFunc(h.ListAllShipments), middleware.Logger, middleware.Gzip, adminOnly))
	mux.Handle("GET /api/v1/shipments/", chain(http.HandlerFunc(h.GetShipmentByOrderID), middleware.Logger, middleware.Gzip, auth))

	mux.Handle("POST /api/v1/payments/webhook", chain(http.HandlerFunc(h.HandlePaymentWebhook), middleware.Logger, middleware.Gzip))

	mux.Handle("POST /api/v1/reviews", chain(http.HandlerFunc(h.CreateReview), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/reviews", chain(http.HandlerFunc(h.ListAllReviews), middleware.Logger, middleware.Gzip, adminOnly))
	mux.Handle("GET /api/v1/dashboard/seller", chain(http.HandlerFunc(h.GetSellerDashboard), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/dashboard/admin", chain(http.HandlerFunc(h.GetAdminDashboard), middleware.Logger, middleware.Gzip, adminOnly))

	mux.Handle("GET /api/v1/admin/reviews", chain(http.HandlerFunc(h.ListAllReviews), middleware.Logger, middleware.Gzip, adminOnly))
	mux.Handle("GET /api/v1/admin/shipments", chain(http.HandlerFunc(h.ListAllShipments), middleware.Logger, middleware.Gzip, adminOnly))

	mux.Handle("POST /api/v1/auth/register", chain(http.HandlerFunc(h.Register), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/register/customer", chain(http.HandlerFunc(h.RegisterCustomer), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/register/tenant", chain(http.HandlerFunc(h.RegisterTenant), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/login", chain(http.HandlerFunc(h.Login), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/logout", chain(http.HandlerFunc(h.Logout), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/password/forgot", chain(http.HandlerFunc(h.ForgotPassword), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/password/reset", chain(http.HandlerFunc(h.ResetPassword), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/refresh-token", chain(http.HandlerFunc(h.RefreshToken), middleware.Logger, middleware.Gzip))
	mux.Handle("POST /api/v1/auth/verify-email", chain(http.HandlerFunc(h.VerifyEmail), middleware.Logger, middleware.Gzip))

	mux.Handle("POST /api/v1/users", chain(http.HandlerFunc(h.CreateUser), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/users", chain(http.HandlerFunc(h.ListUsers), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/users/", chain(http.HandlerFunc(h.GetUser), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("PUT /api/v1/users/", chain(http.HandlerFunc(h.UpdateUser), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("DELETE /api/v1/users/", chain(http.HandlerFunc(h.DeleteUser), middleware.Logger, middleware.Gzip, auth))

	mux.Handle("POST /api/v1/cart", chain(http.HandlerFunc(h.AddToCart), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/cart", chain(http.HandlerFunc(h.GetCart), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("DELETE /api/v1/cart/", chain(http.HandlerFunc(h.DeleteCartItem), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("GET /api/v1/shipping/settings/{vendor_id}", chain(http.HandlerFunc(h.GetShippingSettings), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("PUT /api/v1/shipping/settings/{vendor_id}", chain(http.HandlerFunc(h.UpsertShippingSettings), middleware.Logger, middleware.Gzip, auth))
	mux.Handle("POST /api/v1/shipping/calculate", chain(http.HandlerFunc(h.CalculateShipping), middleware.Logger, middleware.Gzip))

	mux.Handle("GET /docs/", httpSwagger.Handler(httpSwagger.URL("/docs/doc.json")))

	return &Server{cfg: cfg, db: db, mux: mux, svc: svc}, nil
}

// Start runs the HTTP server and ensures the database is reachable.
func (s *Server) Start(ctx context.Context) error {
	if err := s.db.PingContext(ctx); err != nil {
		return err
	}

	repo := postgres.New(s.db)
	if err := repo.ApplyMigrations(ctx); err != nil {
		return err
	}

	// Seed Admin
	if s.cfg.AdminEmail != "" && s.cfg.AdminPassword != "" {
		// Checks if admin already exists
		_, err := repo.GetUserByEmail(ctx, s.cfg.AdminEmail)
		if err != nil {
			// If not found, create
			log.Printf("Seeding admin user: %s", s.cfg.AdminEmail)

			// 1. Create/Get Admin Company
			adminCNPJ := "00000000000000"
			company := &domain.Company{
				ID:            uuid.Must(uuid.NewV7()),
				CNPJ:          adminCNPJ,
				CorporateName: "SaveInMed Admin",
				Category:      "admin",
				LicenseNumber: "ADMIN",
				IsVerified:    true,
				CreatedAt:     time.Now().UTC(),
				UpdatedAt:     time.Now().UTC(),
			}

			// We need to check if company exists by CNPJ normally, but repo doesn't expose GetByCNPJ easily?
			// Let's rely on RegisterAccount handling it or check if we can query.
			// Actually RegisterAccount in Service handles creation if ID is Nil, but keys off ID.
			// We can try to create and ignore conflict, or use a known ID?
			// Let's use RegisterAccount logic.

			// Because RegisterAccount expects us to pass a company, and tries to Get by ID if ID is set, or Create if not.
			// But duplicate CNPJ will fail at DB level.
			// Let's assume on fresh boot it doesn't exist.
			// Or better: Use svc.RegisterAccount. But wait, svc.RegisterAccount logic:
			/*
				if company != nil {
					if company.ID == uuid.Nil {
						// create
					} else {
						// get
					}
				}
			*/
			// If we re-run, GetUserByEmail would have found the user, so we skip.
			// The only edge case is if User was deleted but Company remains.
			// In that case, CreateCompany will fail on CNPJ constraint.

			err := s.svc.RegisterAccount(ctx, company, &domain.User{
				Role:     "Admin",
				Name:     s.cfg.AdminName,
				Username: s.cfg.AdminUsername,
				Email:    s.cfg.AdminEmail,
			}, s.cfg.AdminPassword)

			if err != nil {
				// If error is duplicate key on company, maybe we should fetch the company and try creating user only?
				// For now, let's log error but not fail startup hard, or fail hard to signal issue.
				log.Printf("Failed to seed admin: %v", err)
			} else {
				log.Printf("Admin user created successfully")
			}
		} else {
			log.Printf("Admin user %s already exists", s.cfg.AdminEmail)
		}
	}

	corsConfig := middleware.CORSConfig{AllowedOrigins: s.cfg.CORSOrigins}
	srv := &http.Server{
		Addr:              s.cfg.Addr(),
		Handler:           middleware.SecurityHeaders(middleware.CORSWithConfig(corsConfig)(s.mux)),
		ReadHeaderTimeout: 5 * time.Second,
	}

	log.Printf("starting %s on %s", s.cfg.AppName, s.cfg.Addr())
	return srv.ListenAndServe()
}

func chain(h http.Handler, middlewareFns ...func(http.Handler) http.Handler) http.Handler {
	for i := len(middlewareFns) - 1; i >= 0; i-- {
		h = middlewareFns[i](h)
	}
	return h
}
