package com.magistr.app.controller;

import com.magistr.app.config.auth.AuthContext;
import com.magistr.app.config.auth.JwtProperties;
import com.magistr.app.config.auth.JwtTokenService;
import com.magistr.app.config.auth.ClientIpResolver;
import com.magistr.app.config.auth.LoginRateLimitService;
import com.magistr.app.config.auth.RefreshTokenRotation;
import com.magistr.app.config.auth.RefreshTokenService;
import com.magistr.app.config.tenant.TenantContext;
import com.magistr.app.dto.LoginRequest;
import com.magistr.app.dto.LoginResponse;
import com.magistr.app.model.User;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final LoginRateLimitService loginRateLimitService;
    private final ClientIpResolver clientIpResolver;
    private final JwtTokenService jwtTokenService;
    private final RefreshTokenService refreshTokenService;
    private final JwtProperties jwtProperties;

    private static final Map<String, String> ROLE_REDIRECTS = Map.of(
            "ADMIN", "/admin/",
            "EDUCATION_OFFICE", "/admin/#schedule-view",
            "DEPARTMENT", "/admin/#department-workspace",
            "SCHEDULE_VIEWER", "/admin/#schedule-view",
            "TEACHER", "/teacher/",
            "STUDENT", "/student/"
    );

    public AuthController(LoginRateLimitService loginRateLimitService,
                          ClientIpResolver clientIpResolver,
                          JwtTokenService jwtTokenService,
                          RefreshTokenService refreshTokenService,
                          JwtProperties jwtProperties) {
        this.loginRateLimitService = loginRateLimitService;
        this.clientIpResolver = clientIpResolver;
        this.jwtTokenService = jwtTokenService;
        this.refreshTokenService = refreshTokenService;
        this.jwtProperties = jwtProperties;
    }

    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) {
        LoginRateLimitService.AuthenticationDecision decision = loginRateLimitService.authenticate(
                TenantContext.getCurrentTenant(),
                request.getUsername(),
                request.getPassword(),
                clientIpResolver.resolve(servletRequest)
        );
        if (decision.status() == LoginRateLimitService.AuthenticationDecision.Status.BLOCKED) {
            return ResponseEntity
                    .status(HttpStatus.TOO_MANY_REQUESTS)
                    .header(HttpHeaders.RETRY_AFTER, Long.toString(decision.retryAfterSeconds()))
                    .body(new LoginResponse(
                            false,
                            "Слишком много попыток входа. Повторите позже",
                            null,
                            null,
                            null,
                            null
                    ));
        }
        if (decision.status() == LoginRateLimitService.AuthenticationDecision.Status.REJECTED) {
            return ResponseEntity
                    .status(401)
                    .body(new LoginResponse(
                            false,
                            "Неверное имя пользователя или пароль",
                            null,
                            null,
                            null,
                            null
                    ));
        }

        User user = decision.user();
        String tenant = TenantContext.getCurrentTenant();
        String accessToken = jwtTokenService.createAccessToken(user, tenant);
        String refreshToken = refreshTokenService.createSession(
                user,
                tenant,
                servletRequest.getHeader("User-Agent"),
                clientIpResolver.resolve(servletRequest)
        );

        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, refreshCookie(refreshToken).toString())
                .body(loginResponse(user, accessToken));
    }

    @PostMapping("/refresh")
    public ResponseEntity<LoginResponse> refresh(HttpServletRequest request) {
        Optional<String> refreshToken = refreshCookieValue(request);
        if (refreshToken.isEmpty()) {
            return unauthorized();
        }

        Optional<RefreshTokenRotation> rotation = refreshTokenService.rotate(
                refreshToken.get(),
                TenantContext.getCurrentTenant(),
                request.getHeader("User-Agent"),
                clientIpResolver.resolve(request)
        );
        if (rotation.isEmpty()) {
            return unauthorizedWithClearedCookie();
        }

        User user = rotation.get().user();
        String accessToken = jwtTokenService.createAccessToken(user, TenantContext.getCurrentTenant());
        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, refreshCookie(rotation.get().refreshToken()).toString())
                .body(loginResponse(user, accessToken));
    }

    @PostMapping("/logout")
    public ResponseEntity<Map<String, Object>> logout(HttpServletRequest request) {
        refreshCookieValue(request).ifPresent(token ->
                refreshTokenService.revoke(token, TenantContext.getCurrentTenant())
        );
        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, clearRefreshCookie().toString())
                .body(Map.of("success", true, "message", "Выход выполнен"));
    }

    @GetMapping("/me")
    public ResponseEntity<?> me() {
        var user = AuthContext.getCurrentUser();
        if (user == null) {
            return ResponseEntity.status(401).body(Map.of("message", "Требуется вход в систему"));
        }
        Map<String, Object> response = new LinkedHashMap<>();
        response.put("userId", user.id());
        response.put("username", user.username());
        response.put("role", user.role().name());
        response.put("departmentId", user.departmentId());
        return ResponseEntity.ok(response);
    }

    private LoginResponse loginResponse(User user, String token) {
        String roleName = user.getRole().name();
        return new LoginResponse(
                true,
                "OK",
                token,
                roleName,
                ROLE_REDIRECTS.getOrDefault(roleName, "/"),
                user.getDepartmentId(),
                user.getId()
        );
    }

    private ResponseEntity<LoginResponse> unauthorized() {
        return ResponseEntity
                .status(401)
                .body(new LoginResponse(false, "Требуется вход в систему", null, null, null, null));
    }

    private ResponseEntity<LoginResponse> unauthorizedWithClearedCookie() {
        return ResponseEntity
                .status(401)
                .header(HttpHeaders.SET_COOKIE, clearRefreshCookie().toString())
                .body(new LoginResponse(false, "Требуется вход в систему", null, null, null, null));
    }

    private ResponseCookie refreshCookie(String token) {
        return ResponseCookie.from(jwtProperties.getRefreshCookieName(), token)
                .httpOnly(true)
                .secure(jwtProperties.isRefreshCookieSecure())
                .sameSite("Lax")
                .path("/api/auth")
                .maxAge(jwtProperties.getRefreshTtl())
                .build();
    }

    private ResponseCookie clearRefreshCookie() {
        return ResponseCookie.from(jwtProperties.getRefreshCookieName(), "")
                .httpOnly(true)
                .secure(jwtProperties.isRefreshCookieSecure())
                .sameSite("Lax")
                .path("/api/auth")
                .maxAge(Duration.ZERO)
                .build();
    }

    private Optional<String> refreshCookieValue(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            return Optional.empty();
        }
        for (Cookie cookie : cookies) {
            if (jwtProperties.getRefreshCookieName().equals(cookie.getName())) {
                return Optional.ofNullable(cookie.getValue()).filter(value -> !value.isBlank());
            }
        }
        return Optional.empty();
    }

}
